mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
clean up srp client, still not working
This commit is contained in:
parent
e04ed029de
commit
2ed84ef792
11 changed files with 632 additions and 605 deletions
|
|
@ -11,12 +11,12 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"package": "big-num",
|
||||
"repositoryURL": "https://github.com/adam-fowler/big-num",
|
||||
"package": "BigInt",
|
||||
"repositoryURL": "https://github.com/attaswift/BigInt",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7",
|
||||
"version": "2.0.2"
|
||||
"revision": "793a7fac0bfc318e85994bf6900652e827aef33e",
|
||||
"version": "5.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import SRP
|
|||
import Crypto
|
||||
import CommonCrypto
|
||||
|
||||
|
||||
public class Client {
|
||||
private static let authTypes = ["sa", "hsa", "non-sa", "hsa2"]
|
||||
|
||||
|
|
@ -14,9 +15,8 @@ public class Client {
|
|||
public func srpLogin(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
|
||||
var serviceKey: String!
|
||||
|
||||
let config = SRPConfiguration<SHA256>(.N2048)
|
||||
let client = SRPClient(configuration: config)
|
||||
let clientKeys = client.generateKeys()
|
||||
let client = SRPClient<SHA256>(username: accountName, password: password)
|
||||
let a = client.startAuthentication()
|
||||
|
||||
return Current.network.dataTask(with: URLRequest.itcServiceKey)
|
||||
.map(\.data)
|
||||
|
|
@ -33,13 +33,14 @@ public class Client {
|
|||
}
|
||||
.flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in
|
||||
|
||||
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: clientKeys.private.hex, accountName: accountName))
|
||||
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: a.base64EncodedString(), accountName: accountName))
|
||||
.map(\.data)
|
||||
.decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
|
||||
.map { return (serviceKey, hashcash, $0) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.flatMap { (serviceKey, hashcash, srpInit) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
|
||||
print("SRP INIT REsponse: \(srpInit)")
|
||||
|
||||
guard let decodedB = Data(base64Encoded: srpInit.b) else {
|
||||
return Fail(error: AuthenticationError.srpInvalidPublicKey)
|
||||
|
|
@ -52,46 +53,29 @@ public class Client {
|
|||
}
|
||||
|
||||
let iterations = srpInit.iteration
|
||||
let serverPublic = SRPKey([UInt8](decodedB))
|
||||
|
||||
guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations) else {
|
||||
return Fail(error: AuthenticationError.srpInvalidPublicKey)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
||||
let encryptedPasswordArray = encryptedPassword.hexEncodedString()
|
||||
|
||||
print("EncryptedPassword: \(encryptedPasswordArray)")
|
||||
print("EncryptedPassword: \([UInt8](encryptedPassword))")
|
||||
do {
|
||||
|
||||
// this calculates "S"
|
||||
let clientSharedSecret = try client.calculateSharedSecret(
|
||||
encryptedPassword: encryptedPasswordArray,
|
||||
salt: [UInt8](decodedSalt),
|
||||
clientKeys: clientKeys,
|
||||
serverPublicKey: serverPublic
|
||||
)
|
||||
print("SharedSecret: \(clientSharedSecret)")
|
||||
guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations) else {
|
||||
return Fail(error: AuthenticationError.srpInvalidPublicKey)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let m1 = client.calculateClientProof(
|
||||
username: accountName,
|
||||
salt: [UInt8](decodedSalt),
|
||||
clientPublicKey: clientKeys.public,
|
||||
serverPublicKey: serverPublic,
|
||||
sharedSecret: clientSharedSecret
|
||||
)
|
||||
|
||||
let m2 = client.serverProof(clientProof: m1, clientKeys: clientKeys, sharedSecret: clientSharedSecret)
|
||||
|
||||
// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPassword.hexEncodedString())
|
||||
let encryptedPasswordString = String(data: encryptedPassword, encoding: .utf8)
|
||||
let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPasswordString)
|
||||
|
||||
print("M1: \(Data(m1).base64EncodedString())")
|
||||
print("M2: \(Data(m2).base64EncodedString())")
|
||||
guard let m2 = client.HAMK else {
|
||||
return Fail(error: AuthenticationError.srpInvalidPublicKey)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: Data(m1).base64EncodedString(), m2: Data(m2).base64EncodedString()))
|
||||
.mapError { $0 as Swift.Error }
|
||||
.eraseToAnyPublisher()
|
||||
print("m1: \(m1.base64EncodedString())")
|
||||
print("m2: \(m2.base64EncodedString())")
|
||||
|
||||
return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: m1.base64EncodedString(), m2: m2.base64EncodedString()))
|
||||
.mapError { $0 as Swift.Error }
|
||||
.eraseToAnyPublisher()
|
||||
} catch {
|
||||
print("Error: calculateSharedSecret \(error)")
|
||||
return Fail(error: AuthenticationError.srpInvalidPublicKey)
|
||||
|
|
|
|||
7
xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
34
xcodes-srp/Package.resolved
Normal file
34
xcodes-srp/Package.resolved
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "big-num",
|
||||
"repositoryURL": "https://github.com/adam-fowler/big-num",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7",
|
||||
"version": "2.0.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "BigInt",
|
||||
"repositoryURL": "https://github.com/attaswift/BigInt.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "793a7fac0bfc318e85994bf6900652e827aef33e",
|
||||
"version": "5.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-crypto",
|
||||
"repositoryURL": "https://github.com/apple/swift-crypto",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "ddb07e896a2a8af79512543b1c7eb9797f8898a5",
|
||||
"version": "1.1.7"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
|
|
@ -16,10 +16,10 @@ let package = Package(
|
|||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-crypto", from: "1.0.0"),
|
||||
.package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"),
|
||||
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.0.0")
|
||||
],
|
||||
targets: [
|
||||
.target(name: "SRP", dependencies: ["BigNum", "Crypto"]),
|
||||
.target(name: "SRP", dependencies: ["Crypto", "BigInt"]),
|
||||
.testTarget(
|
||||
name: "SRPTests", dependencies: ["SRP"]),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,182 +1,224 @@
|
|||
import BigNum
|
||||
|
||||
import Foundation
|
||||
import BigInt
|
||||
import Crypto
|
||||
|
||||
/// Manages the client side of Secure Remote Password
|
||||
///
|
||||
/// Secure Remote Password (SRP) provides username and password authentication without needing to provide your password to the server. The server
|
||||
/// has a cryptographic verifier that is derived from the password and a salt that was used to generate this verifier. Both client and server
|
||||
/// generate a shared secret then the client sends a proof they have the secret and if it is correct the server will do the same to verify the
|
||||
/// server as well.
|
||||
///
|
||||
/// This version is compliant with SRP version 6a and RFC 5054.
|
||||
///
|
||||
/// Reference reading
|
||||
/// - https://tools.ietf.org/html/rfc2945
|
||||
/// - https://tools.ietf.org/html/rfc5054
|
||||
///
|
||||
public struct SRPClient<H: HashFunction> {
|
||||
/// configuration. This needs to be the same as the server configuration
|
||||
public let configuration: SRPConfiguration<H>
|
||||
|
||||
/// Initialise a SRPClient object
|
||||
/// - Parameter configuration: configuration to use
|
||||
public init(configuration: SRPConfiguration<H>) {
|
||||
self.configuration = configuration
|
||||
}
|
||||
|
||||
/// Initiate the authentication process
|
||||
/// - Returns: An authentication state. The A value from this state should be sent to the server
|
||||
public func generateKeys() -> SRPKeyPair {
|
||||
var a = BigNum()
|
||||
var A = BigNum()
|
||||
repeat {
|
||||
a = BigNum(bytes: SymmetricKey(size: .bits256))
|
||||
A = configuration.g.power(a, modulus: configuration.N)
|
||||
} while A % configuration.N == BigNum(0)
|
||||
/// SRP Client; the party that initializes the authentication and
|
||||
/// must proof possession of the correct password.
|
||||
public class SRPClient<H: HashFunction> {
|
||||
let a: BigUInt
|
||||
let A: BigUInt
|
||||
|
||||
return SRPKeyPair(public: SRPKey(A), private: SRPKey(a))
|
||||
}
|
||||
|
||||
/// return shared secret given the username, password, B value and salt from the server
|
||||
/// - Parameters:
|
||||
/// - username: user identifier
|
||||
/// - password: password
|
||||
/// - salt: salt
|
||||
/// - clientKeys: client public/private keys
|
||||
/// - serverPublicKey: server public key
|
||||
/// - Throws: `nullServerKey`
|
||||
/// - Returns: shared secret
|
||||
public func calculateSharedSecret(username: String, password: String, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey {
|
||||
let message = [UInt8]("\(username):\(password)".utf8)
|
||||
let sharedSecret = try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
|
||||
return SRPKey(sharedSecret)
|
||||
let group: Group
|
||||
typealias impl = Implementation<H> // swiftlint:disable:this type_name
|
||||
|
||||
let username: String
|
||||
var password: String?
|
||||
var precomputedX: BigUInt?
|
||||
|
||||
public var HAMK: Data?
|
||||
var K: Data?
|
||||
|
||||
/// Whether the session is authenticated, i.e. the password
|
||||
/// was verified by the server and proof of a valid session
|
||||
/// key was provided by the server. If `true`, `sessionKey`
|
||||
/// is also available.
|
||||
public private(set) var isAuthenticated = false
|
||||
|
||||
private init(
|
||||
username: String,
|
||||
group: Group = .N2048,
|
||||
privateKey: Data? = nil)
|
||||
{
|
||||
self.username = username
|
||||
self.group = group
|
||||
if let privateKey = privateKey {
|
||||
a = BigUInt(privateKey)
|
||||
} else {
|
||||
a = BigUInt(Curve25519.KeyAgreement.PrivateKey().rawRepresentation)
|
||||
}
|
||||
// A = g^a % N
|
||||
A = group.g.power(a, modulus: group.N)
|
||||
}
|
||||
|
||||
/// return shared secret given the username, password, B value and salt from the server
|
||||
/// - Parameters:
|
||||
/// - username: user identifier
|
||||
/// - password: password
|
||||
/// - salt: salt
|
||||
/// - clientKeys: client public/private keys
|
||||
/// - serverPublicKey: server public key
|
||||
/// - Throws: `nullServerKey`
|
||||
/// - Returns: shared secret
|
||||
public func calculateSharedSecret(encryptedPassword: String, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey {
|
||||
let message = [UInt8](":\(encryptedPassword)".utf8)
|
||||
let sharedSecret = try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
|
||||
return SRPKey(sharedSecret)
|
||||
}
|
||||
|
||||
|
||||
/// calculate proof of shared secret to send to server
|
||||
/// - Parameters:
|
||||
/// - clientPublicKey: client public key
|
||||
/// - serverPublicKey: server public key
|
||||
/// - sharedSecret: shared secret
|
||||
/// - Returns: The client verification code which should be passed to the server
|
||||
public func calculateSimpleClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
|
||||
// get verification code
|
||||
return SRP<H>.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret)
|
||||
}
|
||||
|
||||
/// If the server returns that the client verification code was valiid it will also return a server verification code that the client can use to verify the server is correct
|
||||
/// Initialize the Client SRP party with a password.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - code: Verification code returned by server
|
||||
/// - state: Authentication state
|
||||
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
|
||||
public func verifySimpleServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
|
||||
// get out version of server proof
|
||||
let HAMS = SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret)
|
||||
// is it the same
|
||||
guard serverProof == HAMS else { throw SRPClientError.invalidServerCode }
|
||||
/// - username: user's username.
|
||||
/// - password: user's password.
|
||||
/// - group: which `Group` to use, must be the same for the
|
||||
/// server as well as the pre-stored verificationKey.
|
||||
/// - privateKey: (optional) custom private key (a); if providing
|
||||
/// the private key of the `Client`, make sure to provide a
|
||||
/// good random key of at least 32 bytes. Default is to
|
||||
/// generate a private key of 128 bytes. You MUST not re-use
|
||||
/// the private key between sessions.
|
||||
public convenience init(
|
||||
username: String,
|
||||
password: String,
|
||||
group: Group = .N2048,
|
||||
privateKey: Data? = nil)
|
||||
{
|
||||
self.init(username: username, group: group, privateKey: privateKey)
|
||||
self.password = password
|
||||
}
|
||||
|
||||
/// calculate proof of shared secret to send to server
|
||||
/// - Parameters:
|
||||
/// - username: username
|
||||
/// - salt: The salt value associated with the user returned by the server
|
||||
/// - clientPublicKey: client public key
|
||||
/// - serverPublicKey: server public key
|
||||
/// - sharedSecret: shared secret
|
||||
/// - Returns: The client verification code which should be passed to the server
|
||||
public func calculateClientProof(username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
|
||||
|
||||
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
|
||||
|
||||
// get verification code
|
||||
return SRP<H>.calculateClientProof(configuration: configuration, username: username, salt: salt, clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, hashSharedSecret: hashSharedSecret)
|
||||
}
|
||||
|
||||
/// If the server returns that the client verification code was valiid it will also return a server verification code that the client can use to verify the server is correct
|
||||
/// Initialize the Client SRP party with a precomputed x.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - code: Verification code returned by server
|
||||
/// - state: Authentication state
|
||||
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
|
||||
public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
|
||||
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
|
||||
// get out version of server proof
|
||||
let HAMK = SRP<H>.calculateServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: hashSharedSecret)
|
||||
// is it the same
|
||||
guard serverProof == HAMK else { throw SRPClientError.invalidServerCode }
|
||||
/// - username: user's username.
|
||||
/// - precomputedX: precomputed SRP x.
|
||||
/// - group: which `Group` to use, must be the same for the
|
||||
/// server as well as the pre-stored verificationKey.
|
||||
/// - privateKey: (optional) custom private key (a); if providing
|
||||
/// the private key of the `Client`, make sure to provide a
|
||||
/// good random key of at least 32 bytes. Default is to
|
||||
/// generate a private key of 128 bytes. You MUST not re-use
|
||||
/// the private key between sessions.
|
||||
public convenience init(
|
||||
username: String,
|
||||
precomputedX: Data,
|
||||
group: Group = .N2048,
|
||||
privateKey: Data? = nil)
|
||||
{
|
||||
self.init(username: username, group: group, privateKey: privateKey)
|
||||
self.precomputedX = BigUInt(precomputedX)
|
||||
}
|
||||
|
||||
|
||||
/// If the server returns that the client verification code was valiid it will also return a server verification code that the client can use to verify the server is correct
|
||||
/// Starts authentication. This method is a no-op.
|
||||
///
|
||||
/// - Returns: `publicKey` (A)
|
||||
public func startAuthentication() -> Data {
|
||||
return publicKey
|
||||
}
|
||||
|
||||
/// Process the challenge provided by the server. This sets the `sessionKey`
|
||||
/// and generates proof that it generated the correct key from the password
|
||||
/// and the challenge. After the server has also proven the validity of their
|
||||
/// key, the `sessionKey` can be used.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - code: Verification code returned by server
|
||||
/// - state: Authentication state
|
||||
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
|
||||
public func serverProof(clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) -> [UInt8] {
|
||||
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
|
||||
/// - salt: user-specific salt (s)
|
||||
/// - publicKey: server's public key (B)
|
||||
/// - Returns: key proof (M)
|
||||
/// - Throws: `AuthenticationFailure.invalidPublicKey` if the server's
|
||||
/// public key is invalid (i.e. B % N is zero).
|
||||
public func processChallenge(salt: Data, publicKey serverPublicKey: Data, isEncryptedPassword: Bool, encryptedPassword: String? = nil) throws -> Data {
|
||||
let H = impl.H
|
||||
let N = group.N
|
||||
|
||||
let B = BigUInt(serverPublicKey)
|
||||
|
||||
guard B % N != 0 else {
|
||||
throw AuthenticationFailure.invalidPublicKey
|
||||
}
|
||||
|
||||
let u = impl.calculate_u(group: group, A: publicKey, B: serverPublicKey)
|
||||
let k = impl.calculate_k(group: group)
|
||||
|
||||
return SRP<H>.calculateServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: hashSharedSecret)
|
||||
let x = self.precomputedX ?? (isEncryptedPassword ? impl.calculate_x(salt: salt, username: "", password: encryptedPassword!) : impl.calculate_x(salt: salt, username: username, password: password!))
|
||||
let v = calculate_v(group: group, x: x)
|
||||
|
||||
// shared secret
|
||||
// S = (B - kg^x) ^ (a + ux)
|
||||
// Note that v = g^x, and that B - kg^x might become negative, which
|
||||
// cannot be stored in BigUInt. So we'll add N to B_ and make sure kv
|
||||
// isn't greater than N.
|
||||
let S = (B + N - k * v % N).power(a + u * x, modulus: N)
|
||||
|
||||
// session key
|
||||
K = H(S.serialize())
|
||||
|
||||
// client verification
|
||||
let M = impl.calculate_M(group: group, username: username, salt: salt, A: publicKey, B: serverPublicKey, K: K!)
|
||||
|
||||
// server verification
|
||||
HAMK = impl.calculate_HAMK(A: publicKey, M: M, K: K!)
|
||||
return M
|
||||
}
|
||||
|
||||
/// Generate salt and password verifier from username and password. When creating your user instead of passing your password to the server, you
|
||||
/// pass the salt and password verifier values. In this way the server never knows your password so can never leak it.
|
||||
|
||||
/// After the server has verified that the password is correct,
|
||||
/// it will send proof of the derived session key. This is verified
|
||||
/// on our end and finalizes the authentication session. After this
|
||||
/// step, the `sessionKey` is available.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - username: username
|
||||
/// - password: user password
|
||||
/// - Returns: tuple containing salt and password verifier
|
||||
public func generateSaltAndVerifier(username: String, password: String) -> (salt: [UInt8], verifier: SRPKey) {
|
||||
let salt = [UInt8].random(count: 16)
|
||||
let verifier = generatePasswordVerifier(username: username, password: password, salt: salt)
|
||||
return (salt: salt, verifier: SRPKey(verifier))
|
||||
/// - Parameter HAMK: proof of the server that it derived the same
|
||||
/// session key.
|
||||
/// - Throws:
|
||||
/// - `AuthenticationFailure.missingChallenge` if this method
|
||||
/// is called before calling `processChallenge`.
|
||||
/// - `AuthenticationFailure.keyProofMismatch` if the proof
|
||||
/// doesn't match our own.
|
||||
public func verifySession(keyProof serverKeyProof: Data) throws {
|
||||
guard let HAMK = HAMK else {
|
||||
throw AuthenticationFailure.missingChallenge
|
||||
}
|
||||
guard HAMK == serverKeyProof else {
|
||||
throw AuthenticationFailure.keyProofMismatch
|
||||
}
|
||||
isAuthenticated = true
|
||||
}
|
||||
|
||||
/// The client's public key (A). For every authentication
|
||||
/// session a new public key is generated.
|
||||
public var publicKey: Data {
|
||||
return A.serialize()
|
||||
}
|
||||
|
||||
/// The client's private key (a). For every authentication
|
||||
/// session a new random private key is generated.
|
||||
public var privateKey: Data {
|
||||
return a.serialize()
|
||||
}
|
||||
|
||||
/// The session key (K) that is exchanged during authentication.
|
||||
/// This key can be used to encrypt further communication
|
||||
/// between client and server.
|
||||
public var sessionKey: Data? {
|
||||
guard isAuthenticated else {
|
||||
return nil
|
||||
}
|
||||
return K
|
||||
}
|
||||
}
|
||||
|
||||
extension SRPClient {
|
||||
/// return shared secret given the username, password, B value and salt from the server
|
||||
func calculateSharedSecret(message: [UInt8], salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> BigNum {
|
||||
guard serverPublicKey.number % configuration.N != BigNum(0) else { throw SRPClientError.nullServerKey }
|
||||
/// Possible authentication failure modes.
|
||||
public enum AuthenticationFailure: Error {
|
||||
/// Security breach: the provided public key is empty (i.e. PK % N is zero).
|
||||
case invalidPublicKey
|
||||
|
||||
// calculate u = H(clientPublicKey | serverPublicKey)
|
||||
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN)
|
||||
/// Invalid client state: call `processChallenge` before `verifySession`.
|
||||
case missingChallenge
|
||||
|
||||
guard u != 0 else { throw SRPClientError.nullServerKey }
|
||||
|
||||
let x = BigNum(bytes: [UInt8](H.hash(data: salt + H.hash(data: message))))
|
||||
|
||||
// calculate S = (B - k*g^x)^(a+u*x)
|
||||
let S = (serverPublicKey.number - configuration.k * configuration.g.power(x, modulus: configuration.N)).power(clientKeys.private.number + u * x, modulus: configuration.N)
|
||||
|
||||
return S
|
||||
}
|
||||
|
||||
/// generate password verifier
|
||||
public func generatePasswordVerifier(username: String, password: String, salt: [UInt8]) -> BigNum {
|
||||
let message = "\(username):\(password)"
|
||||
return generatePasswordVerifier(message: [UInt8](message.utf8), salt: salt)
|
||||
}
|
||||
|
||||
/// generate password verifier
|
||||
public func generatePasswordVerifier(message: [UInt8], salt: [UInt8]) -> BigNum {
|
||||
let x = BigNum(bytes: [UInt8](H.hash(data: salt + H.hash(data: message))))
|
||||
let verifier = configuration.g.power(x, modulus: configuration.N)
|
||||
return verifier
|
||||
/// Failed authentication: the key proof didn't match our own.
|
||||
case keyProofMismatch
|
||||
}
|
||||
|
||||
extension AuthenticationFailure: CustomStringConvertible {
|
||||
/// A textual representation of this instance.
|
||||
///
|
||||
/// Instead of accessing this property directly, convert an instance of any
|
||||
/// type to a string by using the `String(describing:)` initializer.
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .invalidPublicKey: return "security breach - the provided public key is invalid"
|
||||
case .missingChallenge: return "invalid client state - call `processChallenge` before `verifySession`"
|
||||
case .keyProofMismatch: return "failed authentication - the key proof didn't match our own"
|
||||
}
|
||||
}
|
||||
}
|
||||
func ^ (lhs: Data, rhs: Data) -> Data? {
|
||||
guard lhs.count == rhs.count else { return nil }
|
||||
var result = Data(count: lhs.count)
|
||||
for index in lhs.indices {
|
||||
result[index] = lhs[index] ^ rhs[index]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Removed in Xcode 8 beta 3
|
||||
func + (lhs: Data, rhs: Data) -> Data {
|
||||
var result = lhs
|
||||
result.append(rhs)
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,201 +0,0 @@
|
|||
import BigNum
|
||||
import Crypto
|
||||
|
||||
/// SRP Configuration. The same configuration hast to be used by both client and server. Contains a large safe prime N ie a prime where the value (N-1)/2 is also a prime, g the multiplicative group generator and the value k = Hash(N | g)
|
||||
public struct SRPConfiguration<H: HashFunction> {
|
||||
/// large safe prime
|
||||
public let N: BigNum
|
||||
/// multiplicative group generator
|
||||
public let g: BigNum
|
||||
/// derived value from N and g. k = H( N | g )
|
||||
public let k: BigNum
|
||||
/// size in bytes of N
|
||||
public let sizeN: Int
|
||||
|
||||
/// Initialise SRPConfiguration with known safe prime
|
||||
/// - Parameter prime: enum indicating size of prime
|
||||
public init(_ prime: Prime) {
|
||||
self.N = prime.group
|
||||
self.sizeN = Int(self.N.numBits() + 7) / 8
|
||||
self.g = prime.generator
|
||||
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP<H>.pad(self.g.bytes, to: sizeN))))
|
||||
}
|
||||
|
||||
/// Initialise SRPConfiguration with your own prime and multiplicative group generator
|
||||
/// - Parameters:
|
||||
/// - N: Large prime
|
||||
/// - g: multiplicative group generator (usually 2)
|
||||
public init(N: BigNum, g: BigNum) {
|
||||
self.N = N
|
||||
self.sizeN = Int(self.N.numBits() + 7) / 8
|
||||
self.g = g
|
||||
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP<H>.pad(self.g.bytes, to: sizeN))))
|
||||
}
|
||||
|
||||
public enum Prime {
|
||||
case N1024
|
||||
case N1536
|
||||
case N2048
|
||||
case N3072
|
||||
case N4096
|
||||
case N6144
|
||||
case N8192
|
||||
|
||||
/// prime numbers and generators taken from RC 5054 https://tools.ietf.org/html/rfc5054
|
||||
var group: BigNum {
|
||||
switch self {
|
||||
case .N1024:
|
||||
return BigNum(hex:
|
||||
"EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" +
|
||||
"9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4" +
|
||||
"8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29" +
|
||||
"7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A" +
|
||||
"FD5138FE8376435B9FC61D2FC0EB06E3")!
|
||||
|
||||
case .N1536:
|
||||
return BigNum(hex:
|
||||
"9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961" +
|
||||
"4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843" +
|
||||
"80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B" +
|
||||
"E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5" +
|
||||
"6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A" +
|
||||
"F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E" +
|
||||
"8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB")!
|
||||
|
||||
case .N2048:
|
||||
return BigNum(hex:
|
||||
"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294" +
|
||||
"3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D" +
|
||||
"CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB" +
|
||||
"D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74" +
|
||||
"7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A" +
|
||||
"436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D" +
|
||||
"5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73" +
|
||||
"03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6" +
|
||||
"94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" +
|
||||
"9E4AFF73")!
|
||||
|
||||
case .N3072:
|
||||
return BigNum(hex:
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF")!
|
||||
|
||||
case .N4096:
|
||||
return BigNum(hex:
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" +
|
||||
"99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" +
|
||||
"04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" +
|
||||
"233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
|
||||
"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" +
|
||||
"FFFFFFFFFFFFFFFF")!
|
||||
|
||||
case .N6144:
|
||||
return BigNum(hex:
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" +
|
||||
"99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" +
|
||||
"04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" +
|
||||
"233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
|
||||
"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" +
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" +
|
||||
"AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" +
|
||||
"DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" +
|
||||
"2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" +
|
||||
"F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" +
|
||||
"BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" +
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" +
|
||||
"B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" +
|
||||
"387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" +
|
||||
"6DCC4024FFFFFFFFFFFFFFFF")!
|
||||
|
||||
case .N8192:
|
||||
return BigNum(hex:
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" +
|
||||
"99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" +
|
||||
"04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" +
|
||||
"233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
|
||||
"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" +
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" +
|
||||
"AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" +
|
||||
"DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" +
|
||||
"2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" +
|
||||
"F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" +
|
||||
"BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" +
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" +
|
||||
"B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" +
|
||||
"387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" +
|
||||
"6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" +
|
||||
"3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" +
|
||||
"5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" +
|
||||
"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" +
|
||||
"2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" +
|
||||
"6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" +
|
||||
"0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" +
|
||||
"359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" +
|
||||
"FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" +
|
||||
"60C980DD98EDD3DFFFFFFFFFFFFFFFFF")!
|
||||
}
|
||||
}
|
||||
|
||||
var generator: BigNum {
|
||||
switch self {
|
||||
case .N1024, .N1536, .N2048:
|
||||
return BigNum(2)
|
||||
case .N3072, .N4096, .N6144:
|
||||
return BigNum(5)
|
||||
case .N8192:
|
||||
return BigNum(19)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
248
xcodes-srp/Sources/SRP/group.swift
Normal file
248
xcodes-srp/Sources/SRP/group.swift
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
//
|
||||
// must.swift
|
||||
// swift-srp
|
||||
//
|
||||
// Created by Matt Kiazyk on 2024-10-24.
|
||||
//
|
||||
|
||||
|
||||
import BigInt
|
||||
|
||||
/// SRP Group Parameters
|
||||
///
|
||||
/// The 1024-, 1536-, and 2048-bit groups are taken from software
|
||||
/// developed by Tom Wu and Eugene Jhong for the Stanford SRP
|
||||
/// distribution, and subsequently proven to be prime. The larger primes
|
||||
/// are taken from [MODP], but generators have been calculated that are
|
||||
/// primitive roots of N, unlike the generators in [MODP].
|
||||
///
|
||||
/// The values of N and g used in this protocol must be agreed upon by
|
||||
/// the two parties in question. They can be set in advance, or the host
|
||||
/// can supply them to the client. In the latter case, the host should
|
||||
/// send the parameters in the first message along with the salt. For
|
||||
/// maximum security, N should be a safe prime (i.e. a number of the form
|
||||
/// N = 2q + 1, where q is also prime). Also, g should be a generator
|
||||
/// modulo N (see [SRP] for details), which means that for any X where 0
|
||||
/// < X < N, there exists a value x for which g^x % N == X.
|
||||
///
|
||||
/// [MODP] Kivinen, T. and M. Kojo, "More Modular Exponentiation
|
||||
/// (MODP) Diffie-Hellman groups for Internet Key Exchange
|
||||
/// (IKE)", RFC 3526, May 2003.
|
||||
///
|
||||
/// [SRP] T. Wu, "The Secure Remote Password Protocol", In
|
||||
/// Proceedings of the 1998 Internet Society Symposium on
|
||||
/// Network and Distributed Systems Security, San Diego, CA,
|
||||
/// pp. 97-111.
|
||||
public enum Group {
|
||||
/// 1024-bits group
|
||||
case N1024
|
||||
|
||||
/// 2048-bits group
|
||||
case N2048
|
||||
|
||||
/// 1536-bits group
|
||||
case N1536
|
||||
|
||||
/// 3072-bits group
|
||||
case N3072
|
||||
|
||||
/// 4096-bits group
|
||||
case N4096
|
||||
|
||||
/// 6144-bits group
|
||||
case N6144
|
||||
|
||||
/// 8192-bits group
|
||||
case N8192
|
||||
|
||||
/// Custom group parameters. See `init(prime:generator:)` for more information.
|
||||
public struct CustomGroup {
|
||||
let N: BigUInt
|
||||
let g: BigUInt
|
||||
}
|
||||
|
||||
/// Custom group parameters. See `init(prime:generator:)` for more information.
|
||||
case custom(CustomGroup)
|
||||
|
||||
/// Create custom group parameters. See the enum's documentation for
|
||||
/// considerations on good parameters.
|
||||
/// - Parameters:
|
||||
/// - prime: hex-encoded prime
|
||||
/// - generator: hex-encoded generator
|
||||
/// - Returns: nil if one of the parameters chould not be decoded
|
||||
public init?(prime: String, generator: String) {
|
||||
guard let N = BigUInt(prime, radix: 16), let g = BigUInt(generator, radix: 16) else {
|
||||
return nil
|
||||
}
|
||||
self = .custom(CustomGroup(N: N, g: g))
|
||||
}
|
||||
|
||||
var N: BigUInt {
|
||||
switch self {
|
||||
case .N1024:
|
||||
return BigUInt(
|
||||
"EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" +
|
||||
"9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4" +
|
||||
"8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29" +
|
||||
"7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A" +
|
||||
"FD5138FE8376435B9FC61D2FC0EB06E3",
|
||||
radix: 16)!
|
||||
case .N1536:
|
||||
return BigUInt(
|
||||
"9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961" +
|
||||
"4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843" +
|
||||
"80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B" +
|
||||
"E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5" +
|
||||
"6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A" +
|
||||
"F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E" +
|
||||
"8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB",
|
||||
radix: 16)!
|
||||
case .N2048:
|
||||
return BigUInt(
|
||||
"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294" +
|
||||
"3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D" +
|
||||
"CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB" +
|
||||
"D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74" +
|
||||
"7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A" +
|
||||
"436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D" +
|
||||
"5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73" +
|
||||
"03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6" +
|
||||
"94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" +
|
||||
"9E4AFF73",
|
||||
radix: 16)!
|
||||
case .N3072:
|
||||
return BigUInt(
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",
|
||||
radix: 16)!
|
||||
case .N4096:
|
||||
return BigUInt(
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" +
|
||||
"99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" +
|
||||
"04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" +
|
||||
"233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
|
||||
"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" +
|
||||
"FFFFFFFFFFFFFFFF",
|
||||
radix: 16)!
|
||||
case .N6144:
|
||||
return BigUInt(
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" +
|
||||
"99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" +
|
||||
"04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" +
|
||||
"233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
|
||||
"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" +
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" +
|
||||
"AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" +
|
||||
"DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" +
|
||||
"2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" +
|
||||
"F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" +
|
||||
"BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" +
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" +
|
||||
"B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" +
|
||||
"387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" +
|
||||
"6DCC4024FFFFFFFFFFFFFFFF",
|
||||
radix: 16)!
|
||||
case .N8192:
|
||||
return BigUInt(
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" +
|
||||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" +
|
||||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" +
|
||||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" +
|
||||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" +
|
||||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" +
|
||||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" +
|
||||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" +
|
||||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" +
|
||||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" +
|
||||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
|
||||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" +
|
||||
"E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" +
|
||||
"99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" +
|
||||
"04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" +
|
||||
"233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" +
|
||||
"D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" +
|
||||
"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" +
|
||||
"AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" +
|
||||
"DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" +
|
||||
"2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" +
|
||||
"F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" +
|
||||
"BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" +
|
||||
"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" +
|
||||
"B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" +
|
||||
"387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" +
|
||||
"6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" +
|
||||
"3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" +
|
||||
"5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" +
|
||||
"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" +
|
||||
"2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" +
|
||||
"6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" +
|
||||
"0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" +
|
||||
"359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" +
|
||||
"FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" +
|
||||
"60C980DD98EDD3DFFFFFFFFFFFFFFFFF",
|
||||
radix: 16)!
|
||||
case .custom(let custom):
|
||||
return custom.N
|
||||
}
|
||||
}
|
||||
|
||||
var g: BigUInt {
|
||||
switch self {
|
||||
case .N1024:
|
||||
return BigUInt(2)
|
||||
case .N1536:
|
||||
return BigUInt(2)
|
||||
case .N2048:
|
||||
return BigUInt(2)
|
||||
case .N3072:
|
||||
return BigUInt(5)
|
||||
case .N4096:
|
||||
return BigUInt(5)
|
||||
case .N6144:
|
||||
return BigUInt(5)
|
||||
case .N8192:
|
||||
return BigUInt(19)
|
||||
case .custom(let custom):
|
||||
return custom.g
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import BigNum
|
||||
|
||||
/// Wrapper for keys used by SRP
|
||||
public struct SRPKey {
|
||||
public let number: BigNum
|
||||
public var bytes: [UInt8] { number.bytes }
|
||||
public var hex: String { number.hex }
|
||||
|
||||
public init(_ bytes: [UInt8]) {
|
||||
self.number = BigNum(bytes: bytes)
|
||||
}
|
||||
|
||||
public init(_ number: BigNum) {
|
||||
self.number = number
|
||||
}
|
||||
|
||||
public init?(hex: String) {
|
||||
guard let number = BigNum(hex: hex) else { return nil }
|
||||
self.number = number
|
||||
}
|
||||
}
|
||||
|
||||
extension SRPKey: Equatable { }
|
||||
|
||||
/// Contains a private and a public key
|
||||
public struct SRPKeyPair {
|
||||
public let `public`: SRPKey
|
||||
public let `private`: SRPKey
|
||||
|
||||
|
||||
/// Initialise a SRPKeyPair object
|
||||
/// - Parameters:
|
||||
/// - public: The public key of the key pair
|
||||
/// - private: The private key of the key pair
|
||||
public init(`public`: SRPKey, `private`: SRPKey) {
|
||||
self.private = `private`
|
||||
self.public = `public`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
import BigNum
|
||||
import Crypto
|
||||
|
||||
/// Manages the server side of Secure Remote Password.
|
||||
///
|
||||
/// Secure Remote Password (SRP) provides username and password authentication without needing to provide your password to the server. The server
|
||||
/// has a cryptographic verifier that is derived from the password and a salt that was used to generate this verifier. Both client and server
|
||||
/// generate a shared secret then the client sends a proof they have the secret and if it is correct the server will do the same to verify the
|
||||
/// server as well.
|
||||
///
|
||||
/// This version is compliant with SRP version 6a and RFC 5054.
|
||||
///
|
||||
/// Reference reading
|
||||
/// - https://tools.ietf.org/html/rfc2945
|
||||
/// - https://tools.ietf.org/html/rfc5054
|
||||
///
|
||||
public struct SRPServer<H: HashFunction> {
|
||||
/// Authentication state. Stores A,B and shared secret
|
||||
public struct AuthenticationState {
|
||||
let clientPublicKey: SRPKey
|
||||
let serverPublicKey: SRPKey
|
||||
var serverPrivateKey: SRPKey
|
||||
}
|
||||
|
||||
/// configuration has to be the same as the client configuration
|
||||
public let configuration: SRPConfiguration<H>
|
||||
|
||||
/// Initialise SRPServer
|
||||
/// - Parameter configuration: configuration to use
|
||||
public init(configuration: SRPConfiguration<H>) {
|
||||
self.configuration = configuration
|
||||
}
|
||||
|
||||
/// generate public and private keys to be used in srp authentication
|
||||
/// - Parameter verifier: password verifier used to generate key pair
|
||||
/// - Returns: return public/private key pair
|
||||
public func generateKeys(verifier: SRPKey) -> SRPKeyPair {
|
||||
var b: BigNum
|
||||
var B: BigNum
|
||||
repeat {
|
||||
b = BigNum(bytes: SymmetricKey(size: .bits256))
|
||||
B = (configuration.k * verifier.number + configuration.g.power(b, modulus: configuration.N)) % configuration.N
|
||||
} while B % configuration.N == BigNum(0)
|
||||
|
||||
return SRPKeyPair(public: SRPKey(B), private: SRPKey(b))
|
||||
}
|
||||
|
||||
/// calculate the shared secret
|
||||
/// - Parameters:
|
||||
/// - clientPublicKey: public key received from client
|
||||
/// - serverKeys: server key pair
|
||||
/// - verifier: password verifier
|
||||
/// - Returns: shared secret
|
||||
public func calculateSharedSecret(clientPublicKey: SRPKey, serverKeys: SRPKeyPair, verifier: SRPKey) throws -> SRPKey {
|
||||
guard clientPublicKey.number % configuration.N != BigNum(0) else { throw SRPServerError.nullClientKey }
|
||||
|
||||
// calculate u = H(clientPublicKey | serverPublicKey)
|
||||
let u = SRP<H>.calculateU(clientPublicKey: clientPublicKey.bytes, serverPublicKey: serverKeys.public.bytes, pad: configuration.sizeN)
|
||||
|
||||
// calculate S
|
||||
let S = ((clientPublicKey.number * verifier.number.power(u, modulus: configuration.N)).power(serverKeys.private.number, modulus: configuration.N))
|
||||
|
||||
return SRPKey(S)
|
||||
}
|
||||
|
||||
/// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - code: verification code sent by user
|
||||
/// - username: username
|
||||
/// - salt: salt stored with user
|
||||
/// - state: authentication state.
|
||||
/// - Throws: invalidClientCode
|
||||
/// - Returns: The server verification code
|
||||
public func verifySimpleClientProof(proof: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] {
|
||||
let clientProof = SRP<H>.calculateSimpleClientProof(
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverPublicKey: serverPublicKey,
|
||||
sharedSecret: sharedSecret
|
||||
)
|
||||
guard clientProof == proof else { throw SRPServerError.invalidClientProof }
|
||||
return SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret)
|
||||
}
|
||||
|
||||
/// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - code: verification code sent by user
|
||||
/// - username: username
|
||||
/// - salt: salt stored with user
|
||||
/// - state: authentication state.
|
||||
/// - Throws: invalidClientCode
|
||||
/// - Returns: The server verification code
|
||||
public func verifyClientProof(proof: [UInt8], username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] {
|
||||
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
|
||||
|
||||
let clientProof = SRP<H>.calculateClientProof(
|
||||
configuration: configuration,
|
||||
username: username,
|
||||
salt: salt,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverPublicKey: serverPublicKey,
|
||||
hashSharedSecret: hashSharedSecret
|
||||
)
|
||||
guard clientProof == proof else { throw SRPServerError.invalidClientProof }
|
||||
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +1,125 @@
|
|||
import BigNum
|
||||
//
|
||||
// Implementation.swift
|
||||
// swift-srp
|
||||
//
|
||||
// Created by Matt Kiazyk on 2024-10-24.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import BigInt
|
||||
import Crypto
|
||||
|
||||
/// Contains common code used by both client and server SRP code
|
||||
public struct SRP<H: HashFunction> {
|
||||
/// Creates the salted verification key based on a user's username and
|
||||
/// password. Only the salt and verification key need to be stored on the
|
||||
/// server, there's no need to keep the plain-text password.
|
||||
///
|
||||
/// Keep the verification key private, as it can be used to brute-force
|
||||
/// the password from.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - using: hash function to use
|
||||
/// - username: user's username
|
||||
/// - password: user's password
|
||||
/// - salt: (optional) custom salt value; if providing a salt, make sure to
|
||||
/// provide a good random salt of at least 16 bytes. Default is to
|
||||
/// generate a salt of 16 bytes.
|
||||
/// - group: `Group` parameters; default is 2048-bits group.
|
||||
/// - algorithm: which `Digest.Algorithm` to use; default is SHA1.
|
||||
/// - Returns: salt (s) and verification key (v)
|
||||
public func createSaltedVerificationKey<H: HashFunction>(
|
||||
using hashFunction: H.Type,
|
||||
group: Group = .N2048,
|
||||
username: String,
|
||||
password: String,
|
||||
salt: Data? = nil)
|
||||
-> (salt: Data, verificationKey: Data)
|
||||
{
|
||||
let salt = salt ?? randomBytes(16)
|
||||
let x = Implementation<H>.calculate_x(salt: salt, username: username, password: password)
|
||||
return createSaltedVerificationKey(from: x, salt: salt, group: group)
|
||||
}
|
||||
|
||||
/// pad to a certain size by prefixing with zeros
|
||||
static func pad(_ data: [UInt8], to size: Int) -> [UInt8] {
|
||||
let padSize = size - data.count
|
||||
guard padSize > 0 else { return data }
|
||||
// create prefix and return prefix + data
|
||||
let prefix: [UInt8] = (1...padSize).reduce([]) { result,_ in return result + [0] }
|
||||
return prefix + data
|
||||
}
|
||||
|
||||
/// calculate u = H(clientPublicKey | serverPublicKey)
|
||||
public static func calculateU(clientPublicKey: [UInt8], serverPublicKey: [UInt8], pad: Int) -> BigNum {
|
||||
BigNum(bytes: [UInt8].init(H.hash(data: SRP<H>.pad(clientPublicKey, to: pad) + SRP<H>.pad(serverPublicKey, to: pad))))
|
||||
}
|
||||
|
||||
/// Calculate a simpler client verification code H(A | B | S)
|
||||
static func calculateSimpleClientProof(
|
||||
clientPublicKey: SRPKey,
|
||||
serverPublicKey: SRPKey,
|
||||
sharedSecret: SRPKey) -> [UInt8]
|
||||
{
|
||||
let HABK = H.hash(data: clientPublicKey.bytes + serverPublicKey.bytes + sharedSecret.bytes)
|
||||
return [UInt8](HABK)
|
||||
}
|
||||
|
||||
/// Calculate a simpler client verification code H(A | M1 | S)
|
||||
static func calculateSimpleServerVerification(
|
||||
clientPublicKey: SRPKey,
|
||||
clientProof: [UInt8],
|
||||
sharedSecret: SRPKey) -> [UInt8]
|
||||
{
|
||||
let HABK = H.hash(data: clientPublicKey.bytes + clientProof + sharedSecret.bytes)
|
||||
return [UInt8](HABK)
|
||||
/// Creates the salted verification key based on a precomputed SRP x value.
|
||||
/// Only the salt and verification key need to be stored on the
|
||||
/// server, there's no need to keep the plain-text password.
|
||||
///
|
||||
/// Keep the verification key private, as it can be used to brute-force
|
||||
/// the password from.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - x: precomputed SRP x
|
||||
/// - salt: (optional) custom salt value; if providing a salt, make sure to
|
||||
/// provide a good random salt of at least 16 bytes. Default is to
|
||||
/// generate a salt of 16 bytes.
|
||||
/// - group: `Group` parameters; default is 2048-bits group.
|
||||
/// - Returns: salt (s) and verification key (v)
|
||||
public func createSaltedVerificationKey(
|
||||
from x: Data,
|
||||
salt: Data? = nil,
|
||||
group: Group = .N2048)
|
||||
-> (salt: Data, verificationKey: Data)
|
||||
{
|
||||
return createSaltedVerificationKey(from: BigUInt(x), salt: salt, group: group)
|
||||
}
|
||||
|
||||
func createSaltedVerificationKey(
|
||||
from x: BigUInt,
|
||||
salt: Data? = nil,
|
||||
group: Group = .N2048)
|
||||
-> (salt: Data, verificationKey: Data)
|
||||
{
|
||||
let salt = salt ?? randomBytes(16)
|
||||
let v = calculate_v(group: group, x: x)
|
||||
return (salt, v.serialize())
|
||||
}
|
||||
|
||||
func pad(_ data: Data, to size: Int) -> Data {
|
||||
precondition(size >= data.count, "Negative padding not possible")
|
||||
return Data(count: size - data.count) + data
|
||||
}
|
||||
|
||||
enum Implementation<HF: HashFunction> {
|
||||
// swiftlint:disable:next identifier_name
|
||||
static func H(_ data: Data) -> Data {
|
||||
return Data(HF.hash(data: data))
|
||||
}
|
||||
|
||||
/// Calculate client verification code H(H(N)^ H(g)) | H(username) | salt | A | B | H(S))
|
||||
static func calculateClientProof(
|
||||
configuration: SRPConfiguration<H>,
|
||||
username: String,
|
||||
salt: [UInt8],
|
||||
clientPublicKey: SRPKey,
|
||||
serverPublicKey: SRPKey,
|
||||
hashSharedSecret: [UInt8]) -> [UInt8]
|
||||
{
|
||||
// M = H(H(N)^ H(g)) | H(username) | salt | client key | server key | H(shared secret))
|
||||
let N_xor_g = [UInt8](H.hash(data: configuration.N.bytes)) ^ [UInt8](H.hash(data: configuration.g.bytes))
|
||||
let hashUser = H.hash(data: [UInt8](username.utf8))
|
||||
let M1 = [UInt8](N_xor_g) + hashUser + salt
|
||||
let M2 = clientPublicKey.bytes + serverPublicKey.bytes + hashSharedSecret
|
||||
let M = H.hash(data: M1 + M2)
|
||||
return [UInt8](M)
|
||||
//u = H(PAD(A) | PAD(B))
|
||||
static func calculate_u(group: Group, A: Data, B: Data) -> BigUInt {
|
||||
let size = group.N.serialize().count
|
||||
return BigUInt(H(pad(A, to: size) + pad(B, to: size)))
|
||||
}
|
||||
|
||||
/// Calculate server verification code H(A | M1 | K)
|
||||
static func calculateServerVerification(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: [UInt8]) -> [UInt8] {
|
||||
let HAMK = H.hash(data: clientPublicKey.bytes + clientProof + sharedSecret)
|
||||
return [UInt8](HAMK)
|
||||
//M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K)
|
||||
static func calculate_M(group: Group, username: String, salt: Data, A: Data, B: Data, K: Data) -> Data {
|
||||
let HN_xor_Hg = (H(group.N.serialize()) ^ H(group.g.serialize()))!
|
||||
let HI = H(username.data(using: .utf8)!)
|
||||
return H(HN_xor_Hg + HI + salt + A + B + K)
|
||||
}
|
||||
|
||||
//HAMK = H(A | M | K)
|
||||
static func calculate_HAMK(A: Data, M: Data, K: Data) -> Data {
|
||||
return H(A + M + K)
|
||||
}
|
||||
|
||||
//k = H(N | PAD(g))
|
||||
static func calculate_k(group: Group) -> BigUInt {
|
||||
let size = group.N.serialize().count
|
||||
return BigUInt(H(group.N.serialize() + pad(group.g.serialize(), to: size)))
|
||||
}
|
||||
|
||||
//x = H(s | H(I | ":" | P))
|
||||
static func calculate_x(salt: Data, username: String, password: String) -> BigUInt {
|
||||
return BigUInt(H(salt + H("\(username):\(password)".data(using: .utf8)!)))
|
||||
}
|
||||
}
|
||||
|
||||
// v = g^x % N
|
||||
func calculate_v(group: Group, x: BigUInt) -> BigUInt {
|
||||
return group.g.power(x, modulus: group.N)
|
||||
}
|
||||
|
||||
func randomBytes(_ count: Int) -> Data {
|
||||
return Data((0..<count).map { _ in UInt8.random(in: 0...255) })
|
||||
}
|
||||
Loading…
Reference in a new issue