Skip to content

Commit a102917

Browse files
Add support for AES CFB mode in _CryptoExtras
1 parent a53a7e8 commit a102917

16 files changed

+13136
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Crypto
16+
import Foundation
17+
18+
@usableFromInline
19+
typealias AESCFBImpl = OpenSSLAESCFBImpl
20+
21+
extension AES {
22+
public enum _CFB {
23+
@inlinable
24+
public static func encrypt<Plaintext: DataProtocol>(
25+
_ plaintext: Plaintext,
26+
using key: SymmetricKey,
27+
nonce: AES._CFB.Nonce
28+
) throws -> Data {
29+
let bytes: ContiguousBytes = plaintext.regions.count == 1 ? plaintext.regions.first! : Array(plaintext)
30+
return try AESCFBImpl.encryptOrDecrypt(.encrypt, bytes, using: key, nonce: nonce)
31+
}
32+
33+
@inlinable
34+
public static func decrypt<Ciphertext: DataProtocol>(
35+
_ ciphertext: Ciphertext,
36+
using key: SymmetricKey,
37+
nonce: AES._CFB.Nonce
38+
) throws -> Data {
39+
let bytes: ContiguousBytes = ciphertext.regions.count == 1 ? ciphertext.regions.first! : Array(ciphertext)
40+
return try AESCFBImpl.encryptOrDecrypt(.decrypt, bytes, using: key, nonce: nonce)
41+
}
42+
}
43+
}
44+
45+
extension AES._CFB {
46+
public struct Nonce: Sendable {
47+
// 128-bit nonce
48+
private var nonceBytes: (UInt64, UInt64)
49+
50+
public init() {
51+
var rng = SystemRandomNumberGenerator()
52+
self.nonceBytes = (rng.next(), rng.next())
53+
}
54+
55+
public init<NonceBytes: Collection>(nonceBytes: NonceBytes) throws where NonceBytes.Element == UInt8 {
56+
guard nonceBytes.count == 16 else {
57+
throw CryptoKitError.incorrectParameterSize
58+
}
59+
60+
self.nonceBytes = (0, 0)
61+
62+
Swift.withUnsafeMutableBytes(of: &self.nonceBytes) { bytesPtr in
63+
bytesPtr.copyBytes(from: nonceBytes)
64+
}
65+
}
66+
67+
mutating func withUnsafeMutableBytes<ReturnType>(_ body: (UnsafeMutableRawBufferPointer) throws -> ReturnType) rethrows -> ReturnType {
68+
return try Swift.withUnsafeMutableBytes(of: &self.nonceBytes, body)
69+
}
70+
}
71+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@_implementationOnly import CCryptoBoringSSL
16+
import Crypto
17+
import Foundation
18+
19+
@usableFromInline
20+
enum OpenSSLAESCFBImpl {
21+
@usableFromInline
22+
enum Mode {
23+
case encrypt
24+
case decrypt
25+
}
26+
27+
@inlinable
28+
static func encryptOrDecrypt<Plaintext: ContiguousBytes>(
29+
_ mode: Mode,
30+
_ plaintext: Plaintext,
31+
using key: SymmetricKey,
32+
nonce: AES._CFB.Nonce
33+
) throws -> Data {
34+
guard [128, 192, 256].contains(key.bitCount) else {
35+
throw CryptoKitError.incorrectKeySize
36+
}
37+
return plaintext.withUnsafeBytes { plaintextBufferPtr in
38+
Self._encryptOrDecrypt(mode, plaintextBufferPtr, using: key, nonce: nonce)
39+
}
40+
}
41+
42+
@usableFromInline
43+
static func _encryptOrDecrypt(
44+
_ mode: Mode,
45+
_ plaintextBufferPtr: UnsafeRawBufferPointer,
46+
using key: SymmetricKey,
47+
nonce: AES._CFB.Nonce
48+
) -> Data {
49+
var ciphertext = Data(repeating: 0, count: plaintextBufferPtr.count)
50+
ciphertext.withUnsafeMutableBytes { ciphertextBufferPtr in
51+
var nonce = nonce
52+
var num = UInt32.zero
53+
let mode = switch mode {
54+
case .encrypt: AES_ENCRYPT
55+
case .decrypt: AES_DECRYPT
56+
}
57+
key.withUnsafeBytes { keyBufferPtr in
58+
nonce.withUnsafeMutableBytes { nonceBufferPtr in
59+
var key = AES_KEY()
60+
precondition(CCryptoBoringSSL_AES_set_encrypt_key(keyBufferPtr.baseAddress, UInt32(keyBufferPtr.count * 8), &key) == 0)
61+
CCryptoBoringSSL_AES_cfb128_encrypt(
62+
plaintextBufferPtr.baseAddress,
63+
ciphertextBufferPtr.baseAddress,
64+
plaintextBufferPtr.count,
65+
&key,
66+
nonceBufferPtr.baseAddress,
67+
&num,
68+
mode
69+
)
70+
}
71+
}
72+
}
73+
return ciphertext
74+
}
75+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Crypto
16+
@testable import _CryptoExtras
17+
import XCTest
18+
19+
final class AES_CFBTests: XCTestCase {
20+
/// Test vectors from NIST, of the following form:
21+
/// ```
22+
/// COUNT = 0
23+
/// KEY = 00000000000000000000000000000000
24+
/// IV = f34481ec3cc627bacd5dc3fb08f273e6
25+
/// PLAINTEXT = 00000000000000000000000000000000
26+
/// CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e
27+
/// ```
28+
/// —— source: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/block-ciphers
29+
struct TestVector: Codable {
30+
var count: Int
31+
var key: [UInt8]
32+
var iv: [UInt8]
33+
var plaintext: [UInt8]
34+
var ciphertext: [UInt8]
35+
36+
enum CodingKeys: String, CodingKey {
37+
case count = "COUNT"
38+
case key = "KEY"
39+
case iv = "IV"
40+
case plaintext = "PLAINTEXT"
41+
case ciphertext = "CIPHERTEXT"
42+
}
43+
}
44+
45+
func testVector(_ vector: TestVector) throws {
46+
let (contiguousPlaintextData, discontiguousPlaintextData) = vector.plaintext.asDataProtocols()
47+
for plaintextData in [contiguousPlaintextData as DataProtocol, discontiguousPlaintextData as DataProtocol] {
48+
let key = SymmetricKey(data: Data(vector.key))
49+
let nonce = try AES._CFB.Nonce(nonceBytes: vector.iv)
50+
let ciphertext = try AES._CFB.encrypt(plaintextData, using: key, nonce: nonce)
51+
XCTAssertEqual(ciphertext, Data(vector.ciphertext))
52+
}
53+
}
54+
55+
func testVectorsFrom(fileName: String) throws {
56+
var decoder = try RFCVectorDecoder(bundleType: self, fileName: fileName)
57+
let vectors = try decoder.decode([TestVector].self)
58+
for vector in vectors {
59+
try self.testVector(vector)
60+
}
61+
}
62+
63+
func testVectors() throws {
64+
try self.testVectorsFrom(fileName: "AESCFB128GFSbox128")
65+
try self.testVectorsFrom(fileName: "AESCFB128GFSbox128")
66+
try self.testVectorsFrom(fileName: "AESCFB128GFSbox128")
67+
try self.testVectorsFrom(fileName: "AESCFB128GFSbox192")
68+
try self.testVectorsFrom(fileName: "AESCFB128GFSbox256")
69+
try self.testVectorsFrom(fileName: "AESCFB128KeySbox128")
70+
try self.testVectorsFrom(fileName: "AESCFB128KeySbox192")
71+
try self.testVectorsFrom(fileName: "AESCFB128KeySbox256")
72+
try self.testVectorsFrom(fileName: "AESCFB128VarKey128")
73+
try self.testVectorsFrom(fileName: "AESCFB128VarKey192")
74+
try self.testVectorsFrom(fileName: "AESCFB128VarKey256")
75+
try self.testVectorsFrom(fileName: "AESCFB128VarTxt128")
76+
try self.testVectorsFrom(fileName: "AESCFB128VarTxt192")
77+
try self.testVectorsFrom(fileName: "AESCFB128VarTxt256")
78+
}
79+
80+
func testRoundtrip() throws {
81+
let key = SymmetricKey(size: .bits128)
82+
let plaintext = Data(SystemRandomNumberGenerator.randomBytes(count: 1024))
83+
let nonce = AES._CFB.Nonce()
84+
let ciphertext = try AES._CFB.encrypt(plaintext, using: key, nonce: nonce)
85+
XCTAssertEqual(try AES._CFB.decrypt(ciphertext, using: key, nonce: nonce), plaintext)
86+
}
87+
88+
func testRejectsInvalidNonceSizes() throws {
89+
let someBytes = Array(repeating: UInt8(0), count: 24)
90+
91+
for count in 0..<someBytes.count {
92+
let nonceBytes = someBytes.prefix(count)
93+
94+
if count != 16 {
95+
XCTAssertThrowsError(try AES._CFB.Nonce(nonceBytes: nonceBytes))
96+
} else {
97+
XCTAssertNoThrow(try AES._CFB.Nonce(nonceBytes: nonceBytes))
98+
}
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)