Skip to content

Commit

Permalink
Add support for AES CFB mode in _CryptoExtras (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonjbeaumont authored Sep 3, 2024
1 parent a53a7e8 commit 9f95b4d
Show file tree
Hide file tree
Showing 16 changed files with 13,140 additions and 0 deletions.
71 changes: 71 additions & 0 deletions Sources/_CryptoExtras/AES/AES_CFB.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Crypto
import Foundation

@usableFromInline
typealias AESCFBImpl = OpenSSLAESCFBImpl

extension AES {
public enum _CFB {
@inlinable
public static func encrypt<Plaintext: DataProtocol>(
_ plaintext: Plaintext,
using key: SymmetricKey,
iv: AES._CFB.IV
) throws -> Data {
let bytes: ContiguousBytes = plaintext.regions.count == 1 ? plaintext.regions.first! : Array(plaintext)
return try AESCFBImpl.encryptOrDecrypt(.encrypt, bytes, using: key, iv: iv)
}

@inlinable
public static func decrypt<Ciphertext: DataProtocol>(
_ ciphertext: Ciphertext,
using key: SymmetricKey,
iv: AES._CFB.IV
) throws -> Data {
let bytes: ContiguousBytes = ciphertext.regions.count == 1 ? ciphertext.regions.first! : Array(ciphertext)
return try AESCFBImpl.encryptOrDecrypt(.decrypt, bytes, using: key, iv: iv)
}
}
}

extension AES._CFB {
public struct IV: Sendable {
// AES CFB uses a 128-bit IV.
private var ivBytes: (UInt64, UInt64)

public init() {
var rng = SystemRandomNumberGenerator()
self.ivBytes = (rng.next(), rng.next())
}

public init<IVBytes: Collection>(ivBytes: IVBytes) throws where IVBytes.Element == UInt8 {
guard ivBytes.count == 16 else {
throw CryptoKitError.incorrectParameterSize
}

self.ivBytes = (0, 0)

Swift.withUnsafeMutableBytes(of: &self.ivBytes) { bytesPtr in
bytesPtr.copyBytes(from: ivBytes)
}
}

mutating func withUnsafeMutableBytes<ReturnType>(_ body: (UnsafeMutableRawBufferPointer) throws -> ReturnType) rethrows -> ReturnType {
return try Swift.withUnsafeMutableBytes(of: &self.ivBytes, body)
}
}
}
79 changes: 79 additions & 0 deletions Sources/_CryptoExtras/AES/BoringSSL/AES_CFB_boring.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

@_implementationOnly import CCryptoBoringSSL
import Crypto
import Foundation

@usableFromInline
enum OpenSSLAESCFBImpl {
@usableFromInline
enum Mode {
case encrypt
case decrypt

@usableFromInline
var _boringSSLParameter: Int32 {
switch self {
case .encrypt: return AES_ENCRYPT
case .decrypt: return AES_DECRYPT
}
}
}

@inlinable
static func encryptOrDecrypt<Plaintext: ContiguousBytes>(
_ mode: Mode,
_ plaintext: Plaintext,
using key: SymmetricKey,
iv: AES._CFB.IV
) throws -> Data {
guard [128, 192, 256].contains(key.bitCount) else {
throw CryptoKitError.incorrectKeySize
}
return plaintext.withUnsafeBytes { plaintextBufferPtr in
Self._encryptOrDecrypt(mode, plaintextBufferPtr, using: key, iv: iv)
}
}

@usableFromInline
static func _encryptOrDecrypt(
_ mode: Mode,
_ plaintextBufferPtr: UnsafeRawBufferPointer,
using key: SymmetricKey,
iv: AES._CFB.IV
) -> Data {
var ciphertext = Data(repeating: 0, count: plaintextBufferPtr.count)
ciphertext.withUnsafeMutableBytes { ciphertextBufferPtr in
var iv = iv
var num = UInt32.zero
key.withUnsafeBytes { keyBufferPtr in
iv.withUnsafeMutableBytes { ivBufferPtr in
var key = AES_KEY()
precondition(CCryptoBoringSSL_AES_set_encrypt_key(keyBufferPtr.baseAddress, UInt32(keyBufferPtr.count * 8), &key) == 0)
CCryptoBoringSSL_AES_cfb128_encrypt(
plaintextBufferPtr.baseAddress,
ciphertextBufferPtr.baseAddress,
plaintextBufferPtr.count,
&key,
ivBufferPtr.baseAddress,
&num,
mode._boringSSLParameter
)
}
}
}
return ciphertext
}
}
101 changes: 101 additions & 0 deletions Tests/_CryptoExtrasTests/AES_CFBTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Crypto
@testable import _CryptoExtras
import XCTest

final class AES_CFBTests: XCTestCase {
/// Test vectors from NIST, of the following form:
/// ```
/// COUNT = 0
/// KEY = 00000000000000000000000000000000
/// IV = f34481ec3cc627bacd5dc3fb08f273e6
/// PLAINTEXT = 00000000000000000000000000000000
/// CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e
/// ```
/// —— source: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/block-ciphers
struct TestVector: Codable {
var count: Int
var key: [UInt8]
var iv: [UInt8]
var plaintext: [UInt8]
var ciphertext: [UInt8]

enum CodingKeys: String, CodingKey {
case count = "COUNT"
case key = "KEY"
case iv = "IV"
case plaintext = "PLAINTEXT"
case ciphertext = "CIPHERTEXT"
}
}

func testVector(_ vector: TestVector) throws {
let (contiguousPlaintextData, discontiguousPlaintextData) = vector.plaintext.asDataProtocols()
for plaintextData in [contiguousPlaintextData as DataProtocol, discontiguousPlaintextData as DataProtocol] {
let key = SymmetricKey(data: Data(vector.key))
let iv = try AES._CFB.IV(ivBytes: vector.iv)
let ciphertext = try AES._CFB.encrypt(plaintextData, using: key, iv: iv)
XCTAssertEqual(ciphertext, Data(vector.ciphertext))
}
}

func testVectorsFrom(fileName: String) throws {
var decoder = try RFCVectorDecoder(bundleType: self, fileName: fileName)
let vectors = try decoder.decode([TestVector].self)
for vector in vectors {
try self.testVector(vector)
}
}

func testVectors() throws {
try self.testVectorsFrom(fileName: "AESCFB128GFSbox128")
try self.testVectorsFrom(fileName: "AESCFB128GFSbox128")
try self.testVectorsFrom(fileName: "AESCFB128GFSbox128")
try self.testVectorsFrom(fileName: "AESCFB128GFSbox192")
try self.testVectorsFrom(fileName: "AESCFB128GFSbox256")
try self.testVectorsFrom(fileName: "AESCFB128KeySbox128")
try self.testVectorsFrom(fileName: "AESCFB128KeySbox192")
try self.testVectorsFrom(fileName: "AESCFB128KeySbox256")
try self.testVectorsFrom(fileName: "AESCFB128VarKey128")
try self.testVectorsFrom(fileName: "AESCFB128VarKey192")
try self.testVectorsFrom(fileName: "AESCFB128VarKey256")
try self.testVectorsFrom(fileName: "AESCFB128VarTxt128")
try self.testVectorsFrom(fileName: "AESCFB128VarTxt192")
try self.testVectorsFrom(fileName: "AESCFB128VarTxt256")
}

func testRoundtrip() throws {
let key = SymmetricKey(size: .bits128)
let plaintext = Data(SystemRandomNumberGenerator.randomBytes(count: 1024))
let iv = AES._CFB.IV()
let ciphertext = try AES._CFB.encrypt(plaintext, using: key, iv: iv)
XCTAssertEqual(try AES._CFB.decrypt(ciphertext, using: key, iv: iv), plaintext)
}

func testRejectsInvalidIVSizes() throws {
let someBytes = Array(repeating: UInt8(0), count: 24)

for count in 0..<someBytes.count {
let ivBytes = someBytes.prefix(count)

if count != 16 {
XCTAssertThrowsError(try AES._CFB.IV(ivBytes: ivBytes))
} else {
XCTAssertNoThrow(try AES._CFB.IV(ivBytes: ivBytes))
}
}
}
}
Loading

0 comments on commit 9f95b4d

Please sign in to comment.