Skip to content

Commit

Permalink
Update README, formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed Oct 31, 2024
1 parent bd76e88 commit c4341f0
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 268 deletions.
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ let package = Package(
.library(name: "SRP", targets: ["SRP"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-crypto", "1.0.0"..<"5.0.0"),
.package(url: "https://github.com/apple/swift-crypto", "1.0.0" ..< "5.0.0"),
.package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"),
],
targets: [
.target(name: "SRP", dependencies: ["BigNum", "Crypto"]),
.testTarget(
name: "SRPTests", dependencies: ["SRP"]),
name: "SRPTests", dependencies: ["SRP"]
),
]
)
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ let clientSharedSecret = try client.calculateSharedSecret(
let clientProof = client.calculateClientProof(
username: username,
salt: salt,
clientPublicKey: clientKeys.public,
clientPublicKey: clientPublicKey,
serverPublicKey: serverPublicKey,
sharedSecret: clientSharedSecret
)
Expand All @@ -64,7 +64,7 @@ let serverProof = try server.verifyClientProof(
username: username,
salt: salt,
clientPublicKey: clientPublicKey,
serverPublicKey: serverKeys.public,
serverPublicKey: serverPublicKey,
sharedSecret: serverSharedSecret
)
```
Expand All @@ -73,7 +73,7 @@ And finally the client can verify the server proof is valid
try client.verifyServerProof(
serverProof: serverProof,
clientProof: clientProof,
clientKeys: clientKeys,
clientPublicKey: clientPublicKey,
sharedSecret: clientSharedSecret
)
```
Expand All @@ -90,5 +90,3 @@ The library is compliant with RFC5054 and should work with any server implementi
## Proof of secret

For generating the proof above I use the method detailed in [RFC2945](https://tools.ietf.org/html/rfc2945#section-3) but not all servers use this method. For this reason I have kept the sharedSecret generation separate from the proof generation, so you can insert your own version.

I have also supplied a simple proof functions `server.verifySimpleClientProof` and `client.verifySimpleServerProof` which use the proof detailed in the Wikipedia [page](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) on Secure Remote Password if you would prefer to use these.
16 changes: 8 additions & 8 deletions Sources/SRP/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,27 @@ extension Array where Element: FixedWidthInteger {
/// create array of random bytes
static func random(count: Int) -> [Element] {
var array = self.init()
for _ in 0..<count {
array.append(.random(in: Element.min..<Element.max))
for _ in 0 ..< count {
array.append(.random(in: Element.min ..< Element.max))
}
return array
}

/// generate a hexdigest of the array of bytes
func hexdigest() -> String {
return self.map({
return map {
let characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]
return "\(characters[Int($0 >> 4)])\(characters[Int($0 & 0xf)])"
}).joined()
return "\(characters[Int($0 >> 4)])\(characters[Int($0 & 0xF)])"
}.joined()
}
}

extension Array where Element == UInt8 {
func pad(to size: Int) -> [UInt8] {
let padSize = size - self.count
let padSize = size - count
guard padSize > 0 else { return self }
// create prefix and return prefix + data
let prefix: [UInt8] = (1...padSize).reduce([]) { result,_ in return result + [0] }
let prefix: [UInt8] = (1 ... padSize).reduce([]) { result, _ in result + [0] }
return prefix + self
}
}
Expand All @@ -32,7 +32,7 @@ extension Array where Element == UInt8 {
func ^ (lhs: [UInt8], rhs: [UInt8]) -> [UInt8] {
precondition(lhs.count == rhs.count, "Arrays are required to be the same size")
var result = lhs
for i in 0..<lhs.count {
for i in 0 ..< lhs.count {
result[i] = result[i] ^ rhs[i]
}
return result
Expand Down
121 changes: 63 additions & 58 deletions Sources/SRP/client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import Foundation
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 {
Expand All @@ -35,9 +35,9 @@ public struct SRPClient<H: HashFunction> {
A = configuration.g.power(a, modulus: configuration.N)
} while A % configuration.N == BigNum(0)

return SRPKeyPair(public: SRPKey(A, padding: self.configuration.sizeN), private: SRPKey(a))
return SRPKeyPair(public: SRPKey(A, padding: configuration.sizeN), private: SRPKey(a))
}

/// return shared secret given the username, password, B value and salt from the server
/// - Parameters:
/// - username: user identifier
Expand All @@ -48,16 +48,16 @@ public struct SRPClient<H: HashFunction> {
/// - Throws: `nullServerKey`
/// - Returns: shared secret
public func calculateSharedSecret(
username: String,
password: String,
salt: [UInt8],
clientKeys: SRPKeyPair,
username: String,
password: String,
salt: [UInt8],
clientKeys: SRPKeyPair,
serverPublicKey: SRPKey
) throws -> SRPKey {
let message = [UInt8]("\(username):\(password)".utf8)
return try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
}

/// return shared secret given a binary password, B value and salt from the server
/// - Parameters:
/// - password: password
Expand All @@ -67,16 +67,19 @@ public struct SRPClient<H: HashFunction> {
/// - Throws: `nullServerKey`
/// - Returns: shared secret
public func calculateSharedSecret(
password: [UInt8],
salt: [UInt8],
clientKeys: SRPKeyPair,
password: [UInt8],
salt: [UInt8],
clientKeys: SRPKeyPair,
serverPublicKey: SRPKey
) throws -> SRPKey {
let message = [0x3a] + password
let message = [0x3A] + password
return try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
}

/// calculate proof of shared secret to send to server

/// Calculate proof of shared secret to send to server.
///
/// This uses the method detailed in https://tools.ietf.org/html/rfc2945#section-3
///
/// - Parameters:
/// - username: Username
/// - salt: The salt value associated with the user returned by the server
Expand All @@ -85,74 +88,76 @@ public struct SRPClient<H: HashFunction> {
/// - 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,
username: String,
salt: [UInt8],
clientPublicKey: SRPKey,
serverPublicKey: SRPKey,
sharedSecret: SRPKey
) -> [UInt8] {
let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN)
let serverPublicKey = serverPublicKey.with(padding: self.configuration.sizeN)
let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN)
let clientPublicKey = clientPublicKey.with(padding: configuration.sizeN)
let serverPublicKey = serverPublicKey.with(padding: configuration.sizeN)
let sharedSecret = sharedSecret.with(padding: configuration.sizeN)
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,
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

/// 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],
clientPublicKey: SRPKey,
clientProof: [UInt8],
sharedSecret: SRPKey
) -> [UInt8] {
let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN)
let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN)
let clientPublicKey = clientPublicKey.with(padding: configuration.sizeN)
let sharedSecret = sharedSecret.with(padding: configuration.sizeN)
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
// get out version of server proof
return SRP<H>.calculateServerVerification(
clientPublicKey: clientPublicKey,
clientProof: clientProof,
clientPublicKey: clientPublicKey,
clientProof: clientProof,
hashSharedSecret: hashSharedSecret
)
}
/// If the server returns that the client verification code was valid it will also return a server

/// 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 uses the method detailed in https://tools.ietf.org/html/rfc2945#section-3
///
/// - Parameters:
/// - serverProof: Server proof
/// - clientProof: Client proof
/// - clientPublicKey: Client public key
/// - sharedSecret: Shared secret
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
public func verifyServerProof(
serverProof: [UInt8],
clientProof: [UInt8],
clientPublicKey: SRPKey,
serverProof: [UInt8],
clientProof: [UInt8],
clientPublicKey: SRPKey,
sharedSecret: SRPKey
) throws {
// get our version of server proof
let HAMK = calculateServerProof(clientPublicKey: clientPublicKey, 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

/// 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:
Expand All @@ -168,17 +173,17 @@ public struct SRPClient<H: HashFunction> {
/// Hash data using same hash function that SRP uses
/// - Parameter data: Data to be hashed
/// - Returns: Hashed data
@inlinable public func hash<D>(data: D) -> H.Digest where D : DataProtocol {
@inlinable public func hash<D>(data: D) -> H.Digest where D: DataProtocol {
H.hash(data: data)
}
}

extension SRPClient {
public extension SRPClient {
/// return shared secret given the message (username:password), salt from server, client keys, and B value
func calculateSharedSecret(
message: [UInt8],
salt: [UInt8],
clientKeys: SRPKeyPair,
internal func calculateSharedSecret(
message: [UInt8],
salt: [UInt8],
clientKeys: SRPKeyPair,
serverPublicKey: SRPKey
) throws -> SRPKey {
guard serverPublicKey.number % configuration.N != BigNum(0) else { throw SRPClientError.nullServerKey }
Expand All @@ -187,23 +192,23 @@ extension SRPClient {
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes)

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 .init(S, padding: self.configuration.sizeN)

return .init(S, padding: configuration.sizeN)
}

/// generate password verifier
public func generatePasswordVerifier(username: String, password: String, salt: [UInt8]) -> BigNum {
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 {
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
Expand Down
Loading

0 comments on commit c4341f0

Please sign in to comment.