SRP Login works now

- Switch to use https://github.com/adam-fowler/swift-srp with some modifications
  that are local
  - Pad g value to equal size of N while calculating clientProof
- Use SHA256(plain-text-password) while computing key using PBKDF2
- Added a unit test with some sample values
This commit is contained in:
Anand Biligiri 2024-10-28 13:25:17 -07:00
parent 2ed84ef792
commit 9b107ec98c
26 changed files with 778 additions and 676 deletions

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */; };
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 334A932B2CA885A400A5E079 /* LibFido2Swift */; };
3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */; };
332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */; };
@ -196,6 +197,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = "<group>"; };
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = "<group>"; };
36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = "<group>"; };
@ -352,6 +354,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */,
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */,
CABFA9E42592F08E00380FEE /* Version in Frameworks */,
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
@ -416,6 +419,7 @@
CA538A12255A4F7C00E64DD7 /* Frameworks */ = {
isa = PBXGroup;
children = (
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -815,7 +819,7 @@
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */,
15F5B8912CCF44B700705E2F /* XCLocalSwiftPackageReference "swift-srp-main" */,
);
productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
projectDirPath = "";
@ -1485,9 +1489,9 @@
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */ = {
15F5B8912CCF44B700705E2F /* XCLocalSwiftPackageReference "swift-srp-main" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = "xcodes-srp";
relativePath = "swift-srp-main";
};
/* End XCLocalSwiftPackageReference section */

View file

@ -11,12 +11,12 @@
}
},
{
"package": "BigInt",
"repositoryURL": "https://github.com/attaswift/BigInt",
"package": "big-num",
"repositoryURL": "https://github.com/adam-fowler/big-num",
"state": {
"branch": null,
"revision": "793a7fac0bfc318e85994bf6900652e827aef33e",
"version": "5.4.1"
"revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7",
"version": "2.0.2"
}
},
{

View file

@ -12,13 +12,15 @@ let package = Package(
name: "AppleAPI",
targets: ["AppleAPI"]),
],
dependencies: [],
dependencies: [
.package(name: "swift-srp", path: "swift-srp-main")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "AppleAPI",
dependencies: []),
dependencies: [.product(name: "SRP", package: "swift-srp")]),
.testTarget(
name: "AppleAPITests",
dependencies: ["AppleAPI"]),

View file

@ -15,9 +15,10 @@ public class Client {
public func srpLogin(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
var serviceKey: String!
let client = SRPClient<SHA256>(username: accountName, password: password)
let a = client.startAuthentication()
let client = SRPClient(configuration: SRPConfiguration<SHA256>(.N2048))
let clientKeys = client.generateKeys()
let a = clientKeys.public
return Current.network.dataTask(with: URLRequest.itcServiceKey)
.map(\.data)
.decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
@ -33,7 +34,7 @@ public class Client {
}
.flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: a.base64EncodedString(), accountName: accountName))
return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: Data(a.bytes).base64EncodedString(), accountName: accountName))
.map(\.data)
.decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
.map { return (serviceKey, hashcash, $0) }
@ -62,18 +63,16 @@ public class Client {
}
// 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)
let encryptedPasswordString = encryptedPassword.base64EncodedString()
let sharedSecret = try client.calculateSharedSecret(password: encryptedPassword, salt: [UInt8](decodedSalt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))
// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, encryptedPassword: encryptedPasswordString)
guard let m2 = client.HAMK else {
return Fail(error: AuthenticationError.srpInvalidPublicKey)
.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()))
let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](decodedSalt), clientPublicKey: a, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
let m2 = client.calculateServerProof(clientPublicKey: a, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))
print("m1: \(Data(m1).base64EncodedString())")
print("m2: \(Data(m2).base64EncodedString())")
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()
} catch {
@ -382,10 +381,19 @@ public class Client {
.mapError { $0 as Error }
.eraseToAnyPublisher()
}
func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return Data(hash)
}
private func pbkdf2(password: String, saltData: Data, keyByteCount: Int, prf: CCPseudoRandomAlgorithm, rounds: Int) -> Data? {
guard let passwordData = password.data(using: .utf8) else { return nil }
let hashedPasswordData = sha256(data: passwordData)
var derivedKeyData = Data(repeating: 0, count: keyByteCount)
let derivedCount = derivedKeyData.count
let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
@ -393,16 +401,19 @@ public class Client {
derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return saltData.withUnsafeBytes { saltBytes -> Int32 in
let saltBuffer: UnsafePointer<UInt8> = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
password,
passwordData.count,
saltBuffer,
saltData.count,
prf,
UInt32(rounds),
keyBuffer,
derivedCount)
return hashedPasswordData.withUnsafeBytes { hashedPasswordBytes -> Int32 in
let passwordBuffer: UnsafePointer<UInt8> = hashedPasswordBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passwordBuffer,
hashedPasswordData.count,
saltBuffer,
saltData.count,
prf,
UInt32(rounds),
keyBuffer,
derivedCount)
}
}
}
return derivationStatus == kCCSuccess ? derivedKeyData : nil

View file

@ -1,7 +1,11 @@
import Path
import CryptoKit
import Version
@testable import Xcodes
import XCTest
import CommonCrypto
import BigNum
import SRP
class AppStateUpdateTests: XCTestCase {
var subject: AppState!
@ -258,4 +262,81 @@ class AppStateUpdateTests: XCTestCase {
XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0")!, Version("12.3.0-RC")!])
XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[], []])
}
func sha256(data : Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
}
return Data(hash)
}
func testSIRP() throws {
/*
Obtaind by running fastlane spaceauth --verbose -u anand.appleseed@example.com with some custom logging
Starting SIRP Apple ID login
a: {"a":"VLEKLa+n2cyeYNWbECm45CuS4kCdCxodlTDGlW1FKaUyOrv/RbtN2sM0pVE12zI7k3VkocPC3rN5DZBIkahR6I8JHj/J97dtTvzsR+aNRWTYCT2HGP1PBI0QArp3eitAbFqTWI4+Zw+oOnV8+AYdH/wjbq7gOK4C4dvIHE+FzRwIlmguPb5qu2r47R9W3y1msVdoUGlFBOMOMb7Gyq7F0MaEIFH63lNzGomwq74mfss/cFqurd6fxU+Y7tdVTPZw1GWyBEPiXWpk8sNm2zE+S6zWo5tOsICprU75IC9galh1igfzN7VNe0SUFLNFTbFK+Bb1SFAOrAbBZOmyOG5uSQ==","accountName":"anand.appleseed@example.com","protocols":["s2k","s2k_fo"]}
Received SIRP signin init response: {"iteration"=>20309, "salt"=>"fIjNflgqSJXACWwwvhDU+w==", "protocol"=>"s2k", "b"=>"PMbU75wwG6rDTySXn2ASWyfQuPoW5ham15SzIscpInwOPE2uk7sePsW4ra0dHcLDUMFQn/LgBggIKOo7YZ9hf1VReiAzXwSKSHdJHjHUURTC2eNpANGUPO1qzuXYgc/MP3MR+GipKHsz+KTLT+8wLjNaiCIHsL/7evJBMw9QqiwhyXlAIm5mGZfhdTVbGpLz2/QzrFmI6pUTrHpio6m1Q74DH3FBxxIeiIcuEdGdeVt9iUweowBRyf2woasTvSV1fbMQbl+lsWPwzt/a73+J30eOGFdSubqSVYh2pV0OLqRz7zPzJars12teCWUV+0WUIaxb14Mp7tlmqcTPuqZe9w==", "c"=>"d-533-eccbc4e9-9564-11ef-84a6-018111c8cc60:PRN"}
encrypted_password: 40532b4de9353fc537dc62ee84eacebd7ecb5ec26efca98bd01b0380e302100f 32
bb: 7672345903537871991962715758896796468138571329014139041563495295907370682045347022183702983061785424983278857706335295545994877883818377653653442007828499221881058994644619578239367613808278802931379172730746665773282250642455690715139985911303055104847971308813151718669484181874342088801251592138154023949370621963319928723678385968989085032385411532317797659749008135679504901238396934480214258070495365760319076978872181485178648397361564241555189629889320567561713407566532187413091018319494367244540399242523126294027225387432028960726767445027313453858210115810946641002311734776929442587065438110307439763191
x: 726436461883978586175291668001486484510457416477927591386889224605776454162
u: 49415306980415573732801389514223606278850554555635359953422678270536095422201
private 23161374166158551996079451276150657702422963034121842124445818241826569345033578345120284496449280736328513302994568402583647660370960353252836732307301957364261384324957527103960720408713825427474127658415917826326829664923997096839970397226662116904369925262192683131695683487505523842260218490007066160096482662715246662018133837725691586205535995663334471723776536640973591229093933458552240634178864509015968350855952558520147559154646484379002445961375384929682566525908284011230815908584648931495968206840416022306138033496705677078512266958633477047047323620540878121579549170392075029336954975132431417099801
S: 4f75b6ea99c2d7d121357cce80c75c8e1bf74a65e8f66f75f8f66a481301afb8bebf0e54a3fac4f8bfdd60c77d6e670c87968b341f62175e25eb1d4f496e4e6596e1a387f2840688a35002419b70115b7902a46544cc7b31eb4c909c0acaeb752835d1562a687c431421510ebc7535c007a2bd12a4f7696c8c96a75a491b1eb9189ade2bef23dd5b0bb962b4f03e7fba7f6ba6fe67ba34cc18647daf3e474876f85dac5a15eb51c99d1ed78783579ffd6c8b6911f72564d87dc8f76519c8fc1535b83743ed5f7d6b8461d7154ce2a874cbb45bf63018352b9b997fbafbd6b15eac2a544a801c0152470796f3b69a84a4a653e5186b30efeeb148ff3c32ab8e08
K: c5207f707186a52f1adee41bf0a7bc41e51e6dffc25cdaeca8acb7de2710b20a
hN: 65908899099613711898698321155848703477601840791750658211391687862255842366922
hG: 23094592799618609623465742609366149076596436609130823198107630312273622653270
hxor 73599884097654065452785151481733181870375477364472235597514429707329935690908
response: {"accountName":"anand.appleseed@example.com","c":"d-533-eccbc4e9-9564-11ef-84a6-018111c8cc60:PRN","m1":"f/Bkq8gBTYxl7SyiRd4SXTyE/jM/g6E0mVyZIQDIsPg=","m2":"R2rgqC9cMAtWiXUImOrvs4oF+ccibf8KaFsZQ22WokM=","rememberMe":false}
*/
let publicKey = Data(base64Encoded: "VLEKLa+n2cyeYNWbECm45CuS4kCdCxodlTDGlW1FKaUyOrv/RbtN2sM0pVE12zI7k3VkocPC3rN5DZBIkahR6I8JHj/J97dtTvzsR+aNRWTYCT2HGP1PBI0QArp3eitAbFqTWI4+Zw+oOnV8+AYdH/wjbq7gOK4C4dvIHE+FzRwIlmguPb5qu2r47R9W3y1msVdoUGlFBOMOMb7Gyq7F0MaEIFH63lNzGomwq74mfss/cFqurd6fxU+Y7tdVTPZw1GWyBEPiXWpk8sNm2zE+S6zWo5tOsICprU75IC9galh1igfzN7VNe0SUFLNFTbFK+Bb1SFAOrAbBZOmyOG5uSQ==".data(using: .utf8)!)
let clientKeys = SRPKeyPair(public: .init([UInt8](publicKey!)),
private: .init(BigNum("23161374166158551996079451276150657702422963034121842124445818241826569345033578345120284496449280736328513302994568402583647660370960353252836732307301957364261384324957527103960720408713825427474127658415917826326829664923997096839970397226662116904369925262192683131695683487505523842260218490007066160096482662715246662018133837725691586205535995663334471723776536640973591229093933458552240634178864509015968350855952558520147559154646484379002445961375384929682566525908284011230815908584648931495968206840416022306138033496705677078512266958633477047047323620540878121579549170392075029336954975132431417099801")!))
let password = sha256(data: "example".data(using: .utf8)!)
let salt = Data(base64Encoded: "fIjNflgqSJXACWwwvhDU+w==".data(using: .utf8)!)!
let iterations: Int = 20309
let derivedKeyLength: Int = 32
var keyArray = Array<UInt8>(repeating: 0, count: derivedKeyLength)
let result:Int32 = keyArray.withUnsafeMutableBytes { keyBytes -> Int32 in
let keyBuffer = UnsafeMutablePointer<UInt8>(keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self))
return password.withUnsafeBytes { passwordDigestBytes -> Int32 in
let passwordBuffer = UnsafePointer<UInt8>(passwordDigestBytes.baseAddress!.assumingMemoryBound(to: UInt8.self))
return salt.withUnsafeBytes { saltBytes -> Int32 in
let saltBuffer = UnsafePointer<UInt8>(saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self))
return CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passwordBuffer,
password.count,
saltBuffer,
salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
UInt32(iterations),
keyBuffer,
derivedKeyLength)
}
}
}
let expectedKey: [UInt8] = [0x40, 0x53, 0x2b, 0x4d, 0xe9, 0x35, 0x3f, 0xc5, 0x37, 0xdc, 0x62, 0xee, 0x84, 0xea, 0xce, 0xbd, 0x7e, 0xcb, 0x5e, 0xc2, 0x6e, 0xfc, 0xa9, 0x8b, 0xd0, 0x1b, 0x03, 0x80, 0xe3, 0x02, 0x10, 0x0f]
XCTAssertEqual(expectedKey, keyArray)
let decodedB = Data(base64Encoded: "PMbU75wwG6rDTySXn2ASWyfQuPoW5ham15SzIscpInwOPE2uk7sePsW4ra0dHcLDUMFQn/LgBggIKOo7YZ9hf1VReiAzXwSKSHdJHjHUURTC2eNpANGUPO1qzuXYgc/MP3MR+GipKHsz+KTLT+8wLjNaiCIHsL/7evJBMw9QqiwhyXlAIm5mGZfhdTVbGpLz2/QzrFmI6pUTrHpio6m1Q74DH3FBxxIeiIcuEdGdeVt9iUweowBRyf2woasTvSV1fbMQbl+lsWPwzt/a73+J30eOGFdSubqSVYh2pV0OLqRz7zPzJars12teCWUV+0WUIaxb14Mp7tlmqcTPuqZe9w==".data(using: .utf8)!)!
let client = SRPClient(configuration: SRPConfiguration<SHA256>(.N2048))
let sharedSecret = try client.calculateSharedSecret(password: Data(keyArray), salt: [UInt8](salt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))
let accountName = "anand.appleseed@example.com"
let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](salt), clientPublicKey: clientKeys.public, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
let m2 = client.calculateServerProof(clientPublicKey: clientKeys.public, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))
XCTAssertEqual(Data(m1).base64EncodedString(), "f/Bkq8gBTYxl7SyiRd4SXTyE/jM/g6E0mVyZIQDIsPg=")
XCTAssertEqual(Data(m2).base64EncodedString(), "R2rgqC9cMAtWiXUImOrvs4oF+ccibf8KaFsZQ22WokM=")
}
}

1
swift-srp-main/.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: adam-fowler

View file

@ -0,0 +1,39 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
release:
types: [published]
workflow_dispatch:
jobs:
macOS:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Build
run: swift build
- name: Run tests
run: swift test
linux:
runs-on: ubuntu-latest
strategy:
matrix:
image: ['swift:5.9', 'swift:5.10', 'swift:6.0']
container:
image: ${{ matrix.image }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Test
run: swift test --parallel --enable-code-coverage

7
swift-srp-main/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.DS_Store
/.build
/.swiftpm
/Packages
/*.xcodeproj
xcuserdata/
Package.resolved

View file

@ -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/attaswift/BigInt.git", from: "5.0.0")
.package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"),
],
targets: [
.target(name: "SRP", dependencies: ["Crypto", "BigInt"]),
.target(name: "SRP", dependencies: ["BigNum", "Crypto"]),
.testTarget(
name: "SRPTests", dependencies: ["SRP"]),
]

View file

@ -0,0 +1,178 @@
import BigNum
import Crypto
import Foundation
/// 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)
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)
}
public func calculateSharedSecret(password: Data, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey {
let message = [UInt8](Data([0x3a]) + password)
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
///
/// - 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 }
}
/// 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 valid it will also return a server
/// verification code that the client can use to verify the server is correct. This is the calculation
/// to verify it is correct
///
/// - Parameters:
/// - clientPublicKey: Client public key
/// - clientProof: Client proof
/// - sharedSecret: Shared secret
public func calculateServerProof(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: SRPKey) -> [UInt8] {
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
// get out version of server proof
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret)
}
/// If the server returns that the client verification code was valid it will also return a server
/// verification code that the client can use to verify the server is correct
///
/// - Parameters:
/// - clientProof: Server proof
/// - clientProof: Client proof
/// - clientKeys: Client keys
/// - sharedSecret: Shared secret
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
// get our version of server proof
let HAMK = calculateServerProof(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret)
// is it the same
guard serverProof == HAMK else { throw SRPClientError.invalidServerCode }
}
/// 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.
///
/// - 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))
}
}
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 }
// calculate u = H(clientPublicKey | serverPublicKey)
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN)
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
}
}

View file

@ -0,0 +1,201 @@
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)
}
}
}
}

View file

@ -0,0 +1,40 @@
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`
}
}

View file

@ -0,0 +1,110 @@
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:
/// - proof: Client proof
/// - clientPublicKey: Client public key
/// - serverPublicKey: Server public key
/// - sharedSecret: Shared secret
/// - 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
/// - clientPublicKey: Client public key
/// - serverPublicKey: Server public key
/// - sharedSecret: Shared secret
/// - 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)
}
}

View file

@ -0,0 +1,66 @@
import BigNum
import Crypto
/// Contains common code used by both client and server SRP code
public struct SRP<H: HashFunction> {
/// 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)
}
/// 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 hashN = BigNum(bytes: H.hash(data: configuration.N.bytes))
let hashG = BigNum(bytes: H.hash(data: SRP<H>.pad(configuration.g.bytes, to: configuration.sizeN)))
let N_xor_g = hashN.bytes ^ hashG.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)
}
/// 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)
}
}

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -1,34 +0,0 @@
{
"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
}

View file

@ -1,224 +0,0 @@
import Foundation
import BigInt
import Crypto
/// 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
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)
}
/// Initialize the Client SRP party with a password.
///
/// - Parameters:
/// - 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
}
/// Initialize the Client SRP party with a precomputed x.
///
/// - Parameters:
/// - 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)
}
/// 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:
/// - 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)
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
}
/// 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.
///
/// - 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
}
}
/// Possible authentication failure modes.
public enum AuthenticationFailure: Error {
/// Security breach: the provided public key is empty (i.e. PK % N is zero).
case invalidPublicKey
/// Invalid client state: call `processChallenge` before `verifySession`.
case missingChallenge
/// 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
}

View file

@ -1,248 +0,0 @@
//
// 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
}
}
}

View file

@ -1,125 +0,0 @@
//
// Implementation.swift
// swift-srp
//
// Created by Matt Kiazyk on 2024-10-24.
//
import Foundation
import BigInt
import Crypto
/// 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)
}
/// 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))
}
//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)))
}
//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) })
}