From 33dc787e9c3067f17f4836d786abcaa66c11c7c5 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Wed, 17 Dec 2025 09:29:56 +0100 Subject: [PATCH 1/3] Use pointers --- Sources/Bcrypt/EksBlowfish.swift | 138 +++++++++++++++------------- Sources/Bcrypt/Hasher.swift | 39 ++++---- Tests/BcryptTests/BcryptTests.swift | 2 - 3 files changed, 97 insertions(+), 82 deletions(-) diff --git a/Sources/Bcrypt/EksBlowfish.swift b/Sources/Bcrypt/EksBlowfish.swift index 838731d..cdeaa78 100644 --- a/Sources/Bcrypt/EksBlowfish.swift +++ b/Sources/Bcrypt/EksBlowfish.swift @@ -46,40 +46,43 @@ return word } - @usableFromInline static func expand0State(key: [UInt8], p: inout [UInt32], s: inout [UInt32]) { - var j = 0 - var i = 0 - while i < Self.N &+ 2 { - p[i] ^= stream2word(data: key, j: &j) - i &+= 1 - } - - var dataL: UInt32 = 0 - var dataR: UInt32 = 0 - - i = 0 - j = 0 - while i < Self.N &+ 2 { - encipher(xl: &dataL, xr: &dataR, p: p, s: s) - - p[i] = dataL - p[i &+ 1] = dataR - i &+= 2 - } - - i = 0 - while i < 4 { - var k = 0 - while k < 256 { - encipher(xl: &dataL, xr: &dataR, p: p, s: s) - - s[i &* 0x100 &+ k] = dataL - s[i &* 0x100 &+ (k &+ 1)] = dataR - k &+= 2 + p.withUnsafeMutableBufferPointer { pBuf in + s.withUnsafeMutableBufferPointer { sBuf in + let pPtr = pBuf.baseAddress! + let sPtr = sBuf.baseAddress! + + var j = 0 + var i = 0 + while i < Self.N &+ 2 { + pPtr[i] ^= stream2word(data: key, j: &j) + i &+= 1 + } + + var dataL: UInt32 = 0 + var dataR: UInt32 = 0 + + i = 0 + while i < Self.N &+ 2 { + encipher(xl: &dataL, xr: &dataR, p: pPtr, s: sPtr) + pPtr[i] = dataL + pPtr[i &+ 1] = dataR + i &+= 2 + } + + i = 0 + while i < 4 { + var k = 0 + while k < 256 { + encipher(xl: &dataL, xr: &dataR, p: pPtr, s: sPtr) + sPtr[i &* 0x100 &+ k] = dataL + sPtr[i &* 0x100 &+ (k &+ 1)] = dataR + k &+= 2 + } + i &+= 1 + } } - i &+= 1 } } @@ -90,41 +93,48 @@ p: inout [UInt32], s: inout [UInt32] ) { - var j = 0 - var i = 0 - while i < Self.N &+ 2 { - p[i] ^= stream2word(data: password, j: &j) - i &+= 1 - } - - j = 0 - i = 0 - var dataL: UInt32 = 0 - var dataR: UInt32 = 0 - - while i < Self.N &+ 2 { - dataL ^= stream2word(data: salt, j: &j) - dataR ^= stream2word(data: salt, j: &j) - encipher(xl: &dataL, xr: &dataR, p: p, s: s) + p.withUnsafeBufferPointer { pBuf in + s.withUnsafeBufferPointer { sBuf in + let pPtr = pBuf.baseAddress! + let sPtr = sBuf.baseAddress! + + var j = 0 + var i = 0 + while i < Self.N &+ 2 { + p[i] ^= stream2word(data: password, j: &j) + i &+= 1 + } - p[i] = dataL - p[i &+ 1] = dataR - i &+= 2 - } - - i = 0 - while i < 4 { - var k = 0 - while k < 256 { - dataL ^= stream2word(data: salt, j: &j) - dataR ^= stream2word(data: salt, j: &j) - encipher(xl: &dataL, xr: &dataR, p: p, s: s) - - s[i &* 0x100 &+ k] = dataL - s[i &* 0x100 &+ (k &+ 1)] = dataR - k &+= 2 + j = 0 + i = 0 + var dataL: UInt32 = 0 + var dataR: UInt32 = 0 + + while i < Self.N &+ 2 { + dataL ^= stream2word(data: salt, j: &j) + dataR ^= stream2word(data: salt, j: &j) + encipher(xl: &dataL, xr: &dataR, p: pPtr, s: sPtr) + + p[i] = dataL + p[i &+ 1] = dataR + i &+= 2 + } + + i = 0 + while i < 4 { + var k = 0 + while k < 256 { + dataL ^= stream2word(data: salt, j: &j) + dataR ^= stream2word(data: salt, j: &j) + encipher(xl: &dataL, xr: &dataR, p: pPtr, s: sPtr) + + s[i &* 0x100 &+ k] = dataL + s[i &* 0x100 &+ (k &+ 1)] = dataR + k &+= 2 + } + i &+= 1 + } } - i &+= 1 } } diff --git a/Sources/Bcrypt/Hasher.swift b/Sources/Bcrypt/Hasher.swift index b895e84..8d64f23 100644 --- a/Sources/Bcrypt/Hasher.swift +++ b/Sources/Bcrypt/Hasher.swift @@ -83,20 +83,27 @@ extension Bcrypt { i &+= 1 } - i = 0 - while i < 64 { - var j = 0 - var xl: UInt32 = 0 - var xr: UInt32 = 0 - while j < Self.words / 2 { - xl = cData[j &* 2] - xr = cData[j &* 2 &+ 1] - EksBlowfish.encipher(xl: &xl, xr: &xr, p: p, s: s) - cData[j &* 2] = xl - cData[j &* 2 &+ 1] = xr - j &+= 1 + p.withUnsafeBufferPointer { pBuf in + s.withUnsafeBufferPointer { sBuf in + let pPtr = pBuf.baseAddress! + let sPtr = sBuf.baseAddress! + + i = 0 + while i < 64 { + var j = 0 + var xl: UInt32 = 0 + var xr: UInt32 = 0 + while j < Self.words / 2 { + xl = cData[j &* 2] + xr = cData[j &* 2 &+ 1] + EksBlowfish.encipher(xl: &xl, xr: &xr, p: pPtr, s: sPtr) + cData[j &* 2] = xl + cData[j &* 2 &+ 1] = xr + j &+= 1 + } + i &+= 1 + } } - i &+= 1 } var cipherText = Self.cipherText @@ -114,16 +121,16 @@ extension Bcrypt { let cost: [UInt8] = switch cost { case 0...9: - [0x30, UInt8(cost + 0x30)] + [0x30, UInt8(cost &+ 0x30)] default: - [UInt8(cost / 10 + 0x30), UInt8(cost % 10 + 0x30)] + [UInt8(cost / 10 &+ 0x30), UInt8(cost % 10 &+ 0x30)] } let prefix = version.identifier + cost + [36] output += prefix output += salt - output += Base64.encode(cipherText, count: 4 * Self.words - 1) + output += Base64.encode(cipherText, count: 4 &* Self.words &- 1) return output } diff --git a/Tests/BcryptTests/BcryptTests.swift b/Tests/BcryptTests/BcryptTests.swift index 2988c11..23dde42 100644 --- a/Tests/BcryptTests/BcryptTests.swift +++ b/Tests/BcryptTests/BcryptTests.swift @@ -68,10 +68,8 @@ struct BcryptTests { let hash1 = try Bcrypt.hash(password: password, cost: 6) let hash2 = try Bcrypt.hash(password: password, cost: 6) - // Different salts should produce different hashes #expect(hash1 != hash2) - // But both should verify #expect(try Bcrypt.verify(password: password, hash: hash1)) #expect(try Bcrypt.verify(password: password, hash: hash2)) } From f70e3c6dbeb621bf6da834e5ad925b026d1af9e0 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Wed, 17 Dec 2025 09:32:36 +0100 Subject: [PATCH 2/3] Adjust --- Sources/Bcrypt/EksBlowfish.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Bcrypt/EksBlowfish.swift b/Sources/Bcrypt/EksBlowfish.swift index cdeaa78..16ca38e 100644 --- a/Sources/Bcrypt/EksBlowfish.swift +++ b/Sources/Bcrypt/EksBlowfish.swift @@ -93,15 +93,15 @@ p: inout [UInt32], s: inout [UInt32] ) { - p.withUnsafeBufferPointer { pBuf in - s.withUnsafeBufferPointer { sBuf in + p.withUnsafeMutableBufferPointer { pBuf in + s.withUnsafeMutableBufferPointer { sBuf in let pPtr = pBuf.baseAddress! let sPtr = sBuf.baseAddress! var j = 0 var i = 0 while i < Self.N &+ 2 { - p[i] ^= stream2word(data: password, j: &j) + pPtr[i] ^= stream2word(data: password, j: &j) i &+= 1 } @@ -115,8 +115,8 @@ dataR ^= stream2word(data: salt, j: &j) encipher(xl: &dataL, xr: &dataR, p: pPtr, s: sPtr) - p[i] = dataL - p[i &+ 1] = dataR + pPtr[i] = dataL + pPtr[i &+ 1] = dataR i &+= 2 } @@ -128,8 +128,8 @@ dataR ^= stream2word(data: salt, j: &j) encipher(xl: &dataL, xr: &dataR, p: pPtr, s: sPtr) - s[i &* 0x100 &+ k] = dataL - s[i &* 0x100 &+ (k &+ 1)] = dataR + sPtr[i &* 0x100 &+ k] = dataL + sPtr[i &* 0x100 &+ (k &+ 1)] = dataR k &+= 2 } i &+= 1 From 263d221c5bac834c0143e5635a9069351f862a85 Mon Sep 17 00:00:00 2001 From: Paul Toffoloni Date: Wed, 17 Dec 2025 10:13:22 +0100 Subject: [PATCH 3/3] Update measurements --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8d9ec1..1ebd913 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,6 @@ Currently these are the benchmarks for hashing the password "password" with cost | | Release ms | Debug ms | Allocations Release | Allocations Debug | |------|------------|----------|---------------------|-------------------| | vapor/authentication | 215ms | 337ms | ~13,700 | ~13,800 | -| swift-bcrypt | 195ms | 453ms | ~13,400 | ~13,500 | +| swift-bcrypt | 212ms | 454ms | ~13,400 | ~13,400 |