Skip to content

Commit

Permalink
Merge pull request #17 from Bouke/swift-crypto
Browse files Browse the repository at this point in the history
Replace BlueCryptor with SwiftCrypto
  • Loading branch information
Bouke authored Jun 20, 2021
2 parents 78972d5 + 14cb137 commit a702cfc
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 247 deletions.
11 changes: 7 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// swift-tools-version:4.2
// swift-tools-version:5.1

import PackageDescription

let package = Package(
name: "SRP",
platforms: [
.macOS(.v10_15),
],
products: [
.library(name: "SRP", targets: ["SRP"]),
],
dependencies: [
.package(url: "https://github.com/IBM-Swift/BlueCryptor.git", from: "1.0.31"),
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.0.0"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "1.1.0"),
],
targets: [
.target(name: "SRP", dependencies: ["Cryptor", "BigInt"], path: "Sources"),
.testTarget(name: "SRPTests", dependencies: ["Cryptor", "SRP"]),
.target(name: "SRP", dependencies: ["BigInt", "Crypto"], path: "Sources"),
.testTarget(name: "SRPTests", dependencies: ["Crypto", "SRP"]),
]
)
34 changes: 12 additions & 22 deletions Sources/Client.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Foundation
import BigInt
import Cryptor
import Crypto

/// SRP Client; the party that initializes the authentication and
/// must proof possession of the correct password.
public class Client {
public class Client<H: HashFunction> {
let a: BigUInt
let A: BigUInt

let group: Group
let algorithm: Digest.Algorithm
typealias impl = Implementation<H> // swiftlint:disable:this type_name

let username: String
var password: String?
Expand All @@ -27,16 +27,14 @@ public class Client {
private init(
username: String,
group: Group = .N2048,
algorithm: Digest.Algorithm = .sha1,
privateKey: Data? = nil)
{
self.username = username
self.group = group
self.algorithm = algorithm
if let privateKey = privateKey {
a = BigUInt(privateKey)
} else {
a = BigUInt(Data(bytes: try! Random.generate(byteCount: 128)))
a = BigUInt(Curve25519.KeyAgreement.PrivateKey().rawRepresentation)
}
// A = g^a % N
A = group.g.power(a, modulus: group.N)
Expand All @@ -49,9 +47,6 @@ public class Client {
/// - password: user's password.
/// - group: which `Group` to use, must be the same for the
/// server as well as the pre-stored verificationKey.
/// - algorithm: which `Digest.Algorithm` to use, again this
/// 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
Expand All @@ -61,10 +56,9 @@ public class Client {
username: String,
password: String,
group: Group = .N2048,
algorithm: Digest.Algorithm = .sha1,
privateKey: Data? = nil)
{
self.init(username: username, group: group, algorithm: algorithm, privateKey: privateKey)
self.init(username: username, group: group, privateKey: privateKey)
self.password = password
}

Expand All @@ -75,9 +69,6 @@ public class Client {
/// - precomputedX: precomputed SRP x.
/// - group: which `Group` to use, must be the same for the
/// server as well as the pre-stored verificationKey.
/// - algorithm: which `Digest.Algorithm` to use, again this
/// 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
Expand All @@ -87,10 +78,9 @@ public class Client {
username: String,
precomputedX: Data,
group: Group = .N2048,
algorithm: Digest.Algorithm = .sha1,
privateKey: Data? = nil)
{
self.init(username: username, group: group, algorithm: algorithm, privateKey: privateKey)
self.init(username: username, group: group, privateKey: privateKey)
self.precomputedX = BigUInt(precomputedX)
}

Expand All @@ -113,7 +103,7 @@ public class Client {
/// - 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) throws -> Data {
let H = Digest.hasher(algorithm)
let H = impl.H
let N = group.N

let B = BigUInt(serverPublicKey)
Expand All @@ -122,9 +112,9 @@ public class Client {
throw AuthenticationFailure.invalidPublicKey
}

let u = calculate_u(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey)
let k = calculate_k(group: group, algorithm: algorithm)
let x = self.precomputedX ?? calculate_x(algorithm: algorithm, salt: salt, username: username, password: password!)
let u = impl.calculate_u(group: group, A: publicKey, B: serverPublicKey)
let k = impl.calculate_k(group: group)
let x = self.precomputedX ?? impl.calculate_x(salt: salt, username: username, password: password!)
let v = calculate_v(group: group, x: x)

// shared secret
Expand All @@ -138,10 +128,10 @@ public class Client {
K = H(S.serialize())

// client verification
let M = calculate_M(group: group, algorithm: algorithm, username: username, salt: salt, A: publicKey, B: serverPublicKey, K: K!)
let M = impl.calculate_M(group: group, username: username, salt: salt, A: publicKey, B: serverPublicKey, K: K!)

// server verification
HAMK = calculate_HAMK(algorithm: algorithm, A: publicKey, M: M, K: K!)
HAMK = impl.calculate_HAMK(A: publicKey, M: M, K: K!)
return M
}

Expand Down
12 changes: 0 additions & 12 deletions Sources/Cryptor+Extensions.swift

This file was deleted.

79 changes: 43 additions & 36 deletions Sources/SRP.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import BigInt
import Cryptor
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
Expand All @@ -10,6 +10,7 @@ import Cryptor
/// 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
Expand All @@ -18,16 +19,16 @@ import Cryptor
/// - 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(
public func createSaltedVerificationKey<H: HashFunction>(
using hashFunction: H.Type,
group: Group = .N2048,
username: String,
password: String,
salt: Data? = nil,
group: Group = .N2048,
algorithm: Digest.Algorithm = .sha1)
salt: Data? = nil)
-> (salt: Data, verificationKey: Data)
{
let salt = salt ?? Data(bytes: try! Random.generate(byteCount: 16))
let x = calculate_x(algorithm: algorithm, salt: salt, username: username, password: password)
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)
}

Expand Down Expand Up @@ -60,7 +61,7 @@ func createSaltedVerificationKey(
group: Group = .N2048)
-> (salt: Data, verificationKey: Data)
{
let salt = salt ?? Data(bytes: try! Random.generate(byteCount: 16))
let salt = salt ?? randomBytes(16)
let v = calculate_v(group: group, x: x)
return (salt, v.serialize())
}
Expand All @@ -70,41 +71,47 @@ func pad(_ data: Data, to size: Int) -> Data {
return Data(count: size - data.count) + data
}

//u = H(PAD(A) | PAD(B))
func calculate_u(group: Group, algorithm: Digest.Algorithm, A: Data, B: Data) -> BigUInt {
let H = Digest.hasher(algorithm)
let size = group.N.serialize().count
return BigUInt(H(pad(A, to: size) + pad(B, to: size)))
}
enum Implementation<HF: HashFunction> {
// swiftlint:disable:next identifier_name
static func H(_ data: Data) -> Data {
return Data(HF.hash(data: data))
}

//M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K)
func calculate_M(group: Group, algorithm: Digest.Algorithm, username: String, salt: Data, A: Data, B: Data, K: Data) -> Data {
let H = Digest.hasher(algorithm)
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)
}
//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)))
}

//HAMK = H(A | M | K)
func calculate_HAMK(algorithm: Digest.Algorithm, A: Data, M: Data, K: Data) -> Data {
let H = Digest.hasher(algorithm)
return H(A + M + K)
}
//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)
}

//k = H(N | PAD(g))
func calculate_k(group: Group, algorithm: Digest.Algorithm) -> BigUInt {
let H = Digest.hasher(algorithm)
let size = group.N.serialize().count
return BigUInt(H(group.N.serialize() + pad(group.g.serialize(), to: size)))
}
//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))
func calculate_x(algorithm: Digest.Algorithm, salt: Data, username: String, password: String) -> BigUInt {
let H = Digest.hasher(algorithm)
return BigUInt(H(salt + H("\(username):\(password)".data(using: .utf8)!)))
//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) })
}
29 changes: 12 additions & 17 deletions Sources/Server.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Foundation
import BigInt
import Cryptor
import Crypto

/// SRP Server; party that verifies the Client's challenge/response
/// SRP Server; party that verifies the Client's challenge/response
/// against the known password verifier stored for a user.
public class Server {
public class Server<H: HashFunction> {
let b: BigUInt
let B: BigUInt

Expand All @@ -15,7 +15,7 @@ public class Server {
var K: Data?

let group: Group
let algorithm: Digest.Algorithm
typealias impl = Implementation<H> // swiftlint:disable:this type_name

/// Whether the session is authenticated, i.e. the password
/// was verified by the server and proof of a valid session
Expand All @@ -34,9 +34,6 @@ public class Server {
/// username.
/// - group: which `Group` to use, must be the same for the
/// client as well as the pre-stored verificationKey.
/// - algorithm: which `Digest.Algorithm` to use, again this
/// must be the same for the client as well as the pre-stored
/// verificationKey.
/// - privateKey: (optional) custom private key (b); if providing
/// the private key of the `Server`, make sure to provide a
/// good random key of at least 32 bytes. Default is to
Expand All @@ -50,20 +47,18 @@ public class Server {
salt: Data,
verificationKey: Data,
group: Group = .N2048,
algorithm: Digest.Algorithm = .sha1,
privateKey: Data? = nil)
{
self.group = group
self.algorithm = algorithm
self.salt = salt
self.username = username

if let privateKey = privateKey {
b = BigUInt(privateKey)
} else {
b = BigUInt(Data(bytes: try! Random.generate(byteCount: 128)))
b = BigUInt(Curve25519.KeyAgreement.PrivateKey().rawRepresentation)
}
let k = calculate_k(group: group, algorithm: algorithm)
let k = impl.calculate_k(group: group)
v = BigUInt(verificationKey)
let N = group.N
let g = group.g
Expand Down Expand Up @@ -96,7 +91,7 @@ public class Server {
/// - `AuthenticationFailure.keyProofMismatch` if the proof
/// doesn't match our own.
public func verifySession(publicKey clientPublicKey: Data, keyProof clientKeyProof: Data) throws -> Data {
let u = calculate_u(group: group, algorithm: algorithm, A: clientPublicKey, B: publicKey)
let u = impl.calculate_u(group: group, A: clientPublicKey, B: publicKey)
let A = BigUInt(clientPublicKey)
let N = group.N

Expand All @@ -108,17 +103,17 @@ public class Server {
// S = (Av^u) mod N
let S = (A * v.power(u, modulus: N)).power(b, modulus: N)

let H = Digest.hasher(algorithm)
// K = H(S)
let H = impl.H
K = H(S.serialize())

let M = calculate_M(group: group, algorithm: algorithm, username: username, salt: salt, A: clientPublicKey, B: publicKey, K: K!)
let M = impl.calculate_M(group: group, username: username, salt: salt, A: clientPublicKey, B: publicKey, K: K!)
guard clientKeyProof == M else {
throw AuthenticationFailure.keyProofMismatch
}
isAuthenticated = true

return calculate_HAMK(algorithm: algorithm, A: clientPublicKey, M: M, K: sessionKey!)
return impl.calculate_HAMK(A: clientPublicKey, M: M, K: sessionKey!)
}

/// The server's public key (A). For every authentication
Expand All @@ -129,8 +124,8 @@ public class Server {

/// The server's private key (a). For every authentication
/// session a new random private key is generated.
public var privateKey: Data {
return b.serialize()
public var privateKey: Curve25519.KeyAgreement.PrivateKey {
return try! Curve25519.KeyAgreement.PrivateKey(rawRepresentation: b.serialize())
}

/// The session key (K) that is exchanged during authentication.
Expand Down
Loading

0 comments on commit a702cfc

Please sign in to comment.