Auth2.0 PKCE
参考:
- OAuth 2.0 PKCE Flow
- https://www.oauth.com/
- https://www.oauth.com/playground/assets/crypto.js
- Call Your API Using the Authorization Code Flow with PKCE
JavaScript生成code_verifier
和code_challenge
的代码
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<body>
<script src="./crypto.js"></script>
<script>
var code_verifier = random_string(48)
console.log("code_verifier: ", code_verifier)
var code_challenge = base64_urlencode(sha256bin(code_verifier));
console.log("code_challenge: ", code_challenge)
</script>
</body>
</html>
Swift生成code_verifier
和code_challenge
的代码
PKCE.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import Foundation
import CryptoKit
///
/// An easy-to-use implementation of the client side of the [PKCE standard](https://datatracker.ietf.org/doc/html/rfc7636).
///
struct PKCE {
typealias PKCECode = String
/// A high-entropy cryptographic random value, as described in [Section 4.1](https://datatracker.ietf.org/doc/html/rfc7636#section-4.1) of the PKCE standard.
let codeVerifier: PKCECode
/// A transformation of the codeVerifier, as defined in [Section 4.2](https://datatracker.ietf.org/doc/html/rfc7636#section-4.2) of the PKCE standard.
let codeChallenge: PKCECode
init() throws {
codeVerifier = PKCE.generateCodeVerifier()
codeChallenge = try PKCE.codeChallenge(fromVerifier: codeVerifier)
}
static func codeChallenge(fromVerifier verifier: PKCECode) throws -> PKCECode {
guard let verifierData = verifier.data(using: .ascii) else { throw PKCEError.improperlyFormattedVerifier }
let challengeHashed = SHA256.hash(data: verifierData)
let challengeBase64Encoded = Data(challengeHashed).base64URLEncodedString
return challengeBase64Encoded
}
///
/// Generates a random code verifier (as defined in [Seciton 4.1 of the PKCE standard](https://datatracker.ietf.org/doc/html/rfc7636#section-4.1)).
///
/// This method first attempts to use CryptoKit to generate random bytes. If it fails to generate those random bytes, it falls back on a generic
/// Base64 random string generator.
///
static func generateCodeVerifier() -> PKCECode {
do {
let rando = try PKCE.generateCryptographicallySecureRandomOctets(count: 32)
return Data(bytes: rando, count: rando.count).base64URLEncodedString
} catch {
return generateBase64RandomString(ofLength: 43)
}
}
private static func generateCryptographicallySecureRandomOctets(count: Int) throws -> [UInt8] {
var octets = [UInt8](repeating: 0, count: count)
let status = SecRandomCopyBytes(kSecRandomDefault, octets.count, &octets)
if status == errSecSuccess {
return octets
} else {
throw PKCEError.failedToGenerateRandomOctets
}
}
private static func generateBase64RandomString(ofLength length: UInt8) -> PKCECode {
let base64 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in base64.randomElement()! })
}
enum PKCEError: Error {
case failedToGenerateRandomOctets
case improperlyFormattedVerifier
}
}
extension Data {
///
/// Returns a Base64 URL-encoded string _without_ padding.
///
/// This string is compatible with the PKCE Code generation process, and uses the algorithm as defined in the [PKCE standard](https://datatracker.ietf.org/doc/html/rfc7636#appendix-A).
///
var base64URLEncodedString: String {
base64EncodedString()
.replacingOccurrences(of: "=", with: "") // Remove any trailing '='s
.replacingOccurrences(of: "+", with: "-") // 62nd char of encoding
.replacingOccurrences(of: "/", with: "_") // 63rd char of encoding
.trimmingCharacters(in: .whitespaces)
}
}
调用:
1
2
3
4
5
6
7
8
9
do {
let pk = try PKCE()
let code_verifier = pk?.codeVerifier ?? ""
print("code_verifier: ", code_verifier)
let code_challenge = pk?.codeChallenge ?? ""
print("code_challenge: ", code_challenge)
} catch {
print(error)
}
Create code verifier
Javascript sample
1
2
3
4
5
6
7
8
9
// Dependency: Node.js crypto module
// https://nodejs.org/api/crypto.html#crypto_crypto
function base64URLEncode(str) {
return str.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
var verifier = base64URLEncode(crypto.randomBytes(32));
Java sample
1
2
3
4
5
6
7
// See https://developer.android.com/reference/android/util/Base64
// Import the Base64 class
// import android.util.Base64;
SecureRandom sr = new SecureRandom();
byte[] code = new byte[32];
sr.nextBytes(code);
String verifier = Base64.encodeToString(code, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
Swift 5 sample
1
2
3
4
5
6
var buffer = [UInt8](repeating: 0, count: 32)
_ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer)
let verifier = Data(buffer).base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
Objective-C sample
1
2
3
4
5
6
NSMutableData *data = [NSMutableData dataWithLength:32];
int result __attribute__((unused)) = SecRandomCopyBytes(kSecRandomDefault, 32, data.mutableBytes);
NSString *verifier = [[[[data base64EncodedStringWithOptions:0]
stringByReplacingOccurrencesOfString:@"+" withString:@"-"]
stringByReplacingOccurrencesOfString:@"/" withString:@"_"]
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]];
Create code challenge
Javascript sample
1
2
3
4
5
6
// Dependency: Node.js crypto module
// https://nodejs.org/api/crypto.html#crypto_crypto
function sha256(buffer) {
return crypto.createHash('sha256').update(buffer).digest();
}
var challenge = base64URLEncode(sha256(verifier));
Java sample
1
2
3
4
5
6
7
8
9
// Dependency: Apache Commons Codec
// https://commons.apache.org/proper/commons-codec/
// Import the Base64 class.
// import org.apache.commons.codec.binary.Base64;
byte[] bytes = verifier.getBytes("US-ASCII");
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(bytes, 0, bytes.length);
byte[] digest = md.digest();
String challenge = Base64.encodeBase64URLSafeString(digest);
Swift5 sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import CommonCrypto
// ...
guard let data = verifier.data(using: .utf8) else { return nil }
var buffer = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
_ = data.withUnsafeBytes {
CC_SHA256($0.baseAddress, CC_LONG(data.count), &buffer)
}
let hash = Data(buffer)
let challenge = hash.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
Objective-C sample
1
2
3
4
5
6
7
8
9
10
11
// Dependency: Apple Common Crypto library
// http://opensource.apple.com//source/CommonCrypto
u_int8_t buffer[CC_SHA256_DIGEST_LENGTH * sizeof(u_int8_t)];
memset(buffer, 0x0, CC_SHA256_DIGEST_LENGTH);
NSData *data = [verifier dataUsingEncoding:NSUTF8StringEncoding];
CC_SHA256([data bytes], (CC_LONG)[data length], buffer);
NSData *hash = [NSData dataWithBytes:buffer length:CC_SHA256_DIGEST_LENGTH];
NSString *challenge = [[[[hash base64EncodedStringWithOptions:0]
stringByReplacingOccurrencesOfString:@"+" withString:@"-"]
stringByReplacingOccurrencesOfString:@"/" withString:@"_"]
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]];
This post is licensed under CC BY 4.0 by the author.