-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8602a3f
Showing
12 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (C) 2016 Bouke Haarsma | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
of the Software, and to permit persons to whom the Software is furnished to do | ||
so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "SRP", | ||
dependencies: [ | ||
.Package(url: "https://github.com/Bouke/CommonCrypto.git", majorVersion: 1), | ||
.Package(url: "https://github.com/Bouke/Bignum.git", majorVersion: 1), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Secure Remote Password (SRP) for Swift | ||
====================================== | ||
|
||
SRP6 for Swift. API designed similar to the Python package [https://pypi.python.org/pypi/srp](srp). For usage, either see the Python package, or the tests. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import Foundation | ||
import Bignum | ||
import CommonCrypto | ||
|
||
public class Client { | ||
internal let a: Bignum | ||
public let A: Data | ||
|
||
internal let group: Group | ||
internal let alg: Digest | ||
|
||
internal let username: String | ||
internal let password: String | ||
|
||
internal var HAMK: Data? = nil | ||
|
||
public private(set) var isAuthenticated = false | ||
public private(set) var sessionKey: Data? = nil | ||
|
||
public init (group: Group = .N2048, alg: Digest = .SHA1, username: String, password: String, secret: Data? = nil) { | ||
self.group = group | ||
self.alg = alg | ||
self.username = username | ||
self.password = password | ||
|
||
if let secret = secret { | ||
a = Bignum(data: secret) | ||
} else { | ||
a = Bignum(data: generateRandomBytes(count: 32)) | ||
} | ||
// A = g^a % N | ||
A = mod_exp(group.g, a, group.N).data | ||
} | ||
|
||
public func startAuthentication() -> (username: String, A: Data) { | ||
return (username, A) | ||
} | ||
|
||
public func processChallenge(salt: Data, B: Data) -> Data { | ||
let H = alg.hash | ||
let N = group.N | ||
|
||
let u = calculate_u(group: group, alg: alg, A: A, B: B) | ||
let k = calculate_k(group: group, alg: alg) | ||
let x = calculate_x(alg: alg, salt: salt, username: username, password: password) | ||
let v = calculate_v(group: group, x: x) | ||
|
||
let B_ = Bignum(data: B) | ||
|
||
// shared secret | ||
// S = (B - kg^x) ^ (a + ux) | ||
let S = mod_exp(B_ - k * v, a + u * x, N) | ||
|
||
// session key | ||
sessionKey = H(S.data) | ||
|
||
// client verification | ||
let M = calculate_M(group: group, alg: alg, username: username, salt: salt, A: A, B: B, K: sessionKey!) | ||
print(Bignum(data: M)) | ||
|
||
// server verification | ||
HAMK = calculate_HAMK(alg: alg, A: A, M: M, K: sessionKey!) | ||
return M | ||
} | ||
|
||
public func verifySession(HAMK serverHAMK: Data) throws { | ||
guard let HAMK = HAMK else { throw Error.authenticationFailed } | ||
guard HAMK == serverHAMK else { throw Error.authenticationFailed } | ||
isAuthenticated = true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Foundation | ||
|
||
public enum Error: Swift.Error { | ||
case authenticationFailed | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import Bignum | ||
|
||
public enum Group { | ||
case N2048 | ||
case N3072 | ||
|
||
var N: Bignum { | ||
switch self { | ||
case .N2048: | ||
return Bignum(hex: "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + | ||
"A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + | ||
"95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + | ||
"747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + | ||
"8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + | ||
"60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + | ||
"FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73") | ||
case .N3072: | ||
return Bignum(hex: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + | ||
"8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + | ||
"302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + | ||
"A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + | ||
"49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + | ||
"FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + | ||
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + | ||
"180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + | ||
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + | ||
"04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + | ||
"B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + | ||
"1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + | ||
"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + | ||
"E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF") | ||
} | ||
} | ||
|
||
var g: Bignum { | ||
switch self { | ||
case .N2048: | ||
return Bignum(hex: "2") | ||
case .N3072: | ||
return Bignum(hex: "5") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import Foundation | ||
import Bignum | ||
import CommonCrypto | ||
|
||
public func createSaltedVerificationKey(username: String, password: String, salt: Data? = nil, group: Group = .N2048, alg: Digest = .SHA1) -> (salt: Data, verificationKey: Data) { | ||
let salt = salt ?? generateRandomBytes(count: 16) | ||
let x = calculate_x(alg: alg, salt: salt, username: username, password: password) | ||
let v = calculate_v(group: group, x: x) | ||
return (salt, v.data) | ||
} | ||
|
||
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, alg: Digest, A: Data, B: Data) -> Bignum { | ||
let H = alg.hash | ||
return Bignum(data: H(pad(A, to: group.N.data.count) + pad(B, to: group.N.data.count))) | ||
} | ||
|
||
//M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K) | ||
func calculate_M(group: Group, alg: Digest, username: String, salt: Data, A: Data, B: Data, K: Data) -> Data { | ||
let H = alg.hash | ||
let HN_xor_Hg = (H(group.N.data) ^ H(group.g.data))! | ||
let HI = H(username.data(using: .utf8)!) | ||
return H(HN_xor_Hg + HI + salt + A + B + K) | ||
} | ||
|
||
//HAMK = H(A | M | K) | ||
func calculate_HAMK(alg: Digest, A: Data, M: Data, K: Data) -> Data { | ||
let H = alg.hash | ||
return H(A + M + K) | ||
} | ||
|
||
//k = H(N | PAD(g)) | ||
func calculate_k(group: Group, alg: Digest) -> Bignum { | ||
let H = alg.hash | ||
return Bignum(data: H(group.N.data + pad(group.g.data, to: group.N.data.count))) | ||
} | ||
|
||
//x = H(s | H(I | ":" | P)) | ||
func calculate_x(alg: Digest, salt: Data, username: String, password: String) -> Bignum { | ||
let H = alg.hash | ||
return Bignum(data: H(salt + H("\(username):\(password)".data(using: .utf8)!))) | ||
} | ||
|
||
// v = g^x % N | ||
func calculate_v(group: Group, x: Bignum) -> Bignum { | ||
return mod_exp(group.g, x, group.N) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import Foundation | ||
import Bignum | ||
import CommonCrypto | ||
|
||
public class Server { | ||
internal let b: Bignum | ||
public let B: Data | ||
|
||
public let salt: Data | ||
public let username: String | ||
|
||
internal let v: Bignum | ||
|
||
internal let group: Group | ||
internal let alg: Digest | ||
|
||
public private(set) var isAuthenticated = false | ||
public private(set) var sessionKey: Data? = nil | ||
|
||
public init (group: Group = .N2048, alg: Digest = .SHA1, salt: Data, username: String, verificationKey: Data, secret: Data? = nil) { | ||
self.group = group | ||
self.alg = alg | ||
self.salt = salt | ||
self.username = username | ||
|
||
if let secret = secret { | ||
b = Bignum(data: secret) | ||
} else { | ||
b = Bignum(data: generateRandomBytes(count: 32)) | ||
} | ||
let k = calculate_k(group: group, alg: alg) | ||
v = Bignum(data: verificationKey) | ||
let N = group.N | ||
let g = group.g | ||
// B = k*v + g^b % N | ||
B = mod_add(k * v, mod_exp(g, b, N), N).data | ||
} | ||
|
||
public func getChallenge() -> (salt: Data, B: Data) { | ||
return (salt, B) | ||
} | ||
|
||
public func verifySession(A: Data, M clientM: Data) throws -> Data { | ||
let u = calculate_u(group: group, alg: alg, A: A, B: B) | ||
let A_ = Bignum(data: A) | ||
let N = group.N | ||
|
||
// shared secret | ||
// S = (Av^u) mod N | ||
let S = mod_exp(A_ * mod_exp(v, u, N), b, N) | ||
|
||
let H = alg.hash | ||
// K = H(S) | ||
sessionKey = H(S.data) | ||
|
||
let M = calculate_M(group: group, alg: alg, username: username, salt: salt, A: A, B: B, K: sessionKey!) | ||
print(Bignum(data: M)) | ||
guard clientM == M else { throw Error.authenticationFailed } | ||
isAuthenticated = true | ||
|
||
return calculate_HAMK(alg: alg, A: A, M: M, K: sessionKey!) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import XCTest | ||
@testable import SRPTests | ||
|
||
XCTMain([ | ||
testCase(SRPTests.allTests), | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import XCTest | ||
import CommonCrypto | ||
@testable import SRP | ||
|
||
class SRPTests: XCTestCase { | ||
func test() throws { | ||
let username = "Pair-Setup" | ||
let password = "001-02-003" | ||
|
||
/* Create a salt+verification key for the user's password. The salt and | ||
* key need to be computed at the time the user's password is set and | ||
* must be stored by the server-side application for use during the | ||
* authentication process. | ||
*/ | ||
let (salt, verificationKey) = createSaltedVerificationKey(username: username, password: password) | ||
|
||
// Begin authentication process | ||
let client = Client(username: username, password: password) | ||
let (_, A) = client.startAuthentication() | ||
|
||
// Client->Server: I (username) | ||
// Server retrieves salt and verificationKey from permanent storage | ||
let server = Server(salt: salt, username: username, verificationKey: verificationKey, secret: generateRandomBytes(count: 32)) | ||
|
||
// The server generates the challenge: pre-defined salt, public key B | ||
// Server->Client: salt, B | ||
let (_, B) = server.getChallenge() | ||
|
||
// Using (salt, B), the client generates the proof M | ||
// Client->Server: M | ||
let M = client.processChallenge(salt: salt, B: B) | ||
|
||
XCTAssertFalse(server.isAuthenticated) | ||
XCTAssertFalse(client.isAuthenticated) | ||
|
||
let HAMK: Data | ||
do { | ||
// Using M, the server verifies the proof and calculates a proof for the client | ||
// Server->Client: H(AMK) | ||
HAMK = try server.verifySession(A: client.A, M: M) | ||
} catch SRP.Error.authenticationFailed { | ||
return XCTFail("Client generated invalid M") | ||
} | ||
|
||
// At this point, the server is authenticated. | ||
XCTAssert(server.isAuthenticated) | ||
XCTAssertFalse(client.isAuthenticated) | ||
|
||
do { | ||
// Using H(AMK), the client verifies the server's proof | ||
try client.verifySession(HAMK: HAMK) | ||
} catch SRP.Error.authenticationFailed { | ||
return XCTFail("Server generated invalid H(AMK)") | ||
} | ||
|
||
// At this point, the client is authenticated as well | ||
XCTAssert(server.isAuthenticated) | ||
XCTAssert(client.isAuthenticated) | ||
|
||
// They now share a secret session key | ||
guard let K0 = server.sessionKey, let K1 = client.sessionKey else { | ||
return XCTFail("Session keys not set") | ||
} | ||
XCTAssertEqual(K0, K1, "Session keys not equal") | ||
} | ||
|
||
static var allTests : [(String, (SRPTests) -> () throws -> Void)] { | ||
return [ | ||
("test", test), | ||
] | ||
} | ||
} |