diff --git a/Sources/_CryptoExtras/AES/AES_CTR.swift b/Sources/_CryptoExtras/AES/AES_CTR.swift index 682f0d04..4590e28d 100644 --- a/Sources/_CryptoExtras/AES/AES_CTR.swift +++ b/Sources/_CryptoExtras/AES/AES_CTR.swift @@ -15,69 +15,30 @@ import Crypto import Foundation -extension AES { - public enum _CTR { - private static func encryptInPlace( - _ plaintext: UnsafeMutableRawBufferPointer, - using key: SymmetricKey, - nonce: AES._CTR.Nonce - ) throws { - precondition(MemoryLayout.size == 16) - - guard [128, 192, 256].contains(key.bitCount) else { - throw CryptoKitError.incorrectKeySize - } - - var nonce = nonce +@usableFromInline +typealias AESCTRImpl = OpenSSLAESCTRImpl - for offset in stride(from: 0, to: plaintext.count, by: MemoryLayout.size) { - var nonceCopy = nonce - - try nonceCopy.withUnsafeMutableBytes { noncePtr in - var noncePtr = noncePtr - try AES.permute(&noncePtr, key: key) - let remainingPlaintextBytes = plaintext.count &- offset - - for i in 0...size) { - plaintext[offset &+ i] ^= noncePtr[i] - } - } - - nonce.incrementCounter() - } - } +extension AES { + public enum _CTR { + @inlinable public static func encrypt( _ plaintext: Plaintext, using key: SymmetricKey, nonce: AES._CTR.Nonce ) throws -> Data { - var flattenedPlaintext = Data(plaintext) - try flattenedPlaintext.withUnsafeMutableBytes { - try Self.encryptInPlace($0, using: key, nonce: nonce) - } - return flattenedPlaintext - } - - private static func decryptInPlace( - _ ciphertext: UnsafeMutableRawBufferPointer, - using key: SymmetricKey, - nonce: AES._CTR.Nonce - ) throws { - // Surprise, CTR mode is symmetric in encryption/decryption! - try Self.encryptInPlace(ciphertext, using: key, nonce: nonce) + let bytes: ContiguousBytes = plaintext.regions.count == 1 ? plaintext.regions.first! : Array(plaintext) + return try AESCTRImpl.encrypt(bytes, using: key, nonce: nonce) } + @inlinable public static func decrypt( _ ciphertext: Ciphertext, using key: SymmetricKey, nonce: AES._CTR.Nonce ) throws -> Data { - var flattenedCiphertext = Data(ciphertext) - try flattenedCiphertext.withUnsafeMutableBytes { - try Self.decryptInPlace($0, using: key, nonce: nonce) - } - return flattenedCiphertext + // Surprise, CTR mode is symmetric in encryption/decryption! + try Self.encrypt(ciphertext, using: key, nonce: nonce) } } } @@ -114,21 +75,6 @@ extension AES._CTR { } } - mutating func incrementCounter() { - var (newValue, overflow) = UInt32(bigEndian: self.nonceBytes.2).addingReportingOverflow(1) - self.nonceBytes.2 = newValue.bigEndian - - if overflow { - (newValue, overflow) = UInt32(bigEndian: self.nonceBytes.1).addingReportingOverflow(1) - self.nonceBytes.1 = newValue.bigEndian - } - - if overflow { - // If this overflows that's fine: we'll have overflowed everything and gone back to 0. - self.nonceBytes.0 = (UInt64(bigEndian: self.nonceBytes.0) &+ 1).bigEndian - } - } - mutating func withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ReturnType) rethrows -> ReturnType { return try Swift.withUnsafeMutableBytes(of: &self.nonceBytes, body) } diff --git a/Sources/_CryptoExtras/AES/BoringSSL/AES_CTR_boring.swift b/Sources/_CryptoExtras/AES/BoringSSL/AES_CTR_boring.swift new file mode 100644 index 00000000..8ed5a9ef --- /dev/null +++ b/Sources/_CryptoExtras/AES/BoringSSL/AES_CTR_boring.swift @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// +// 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 OpenSSLAESCTRImpl { + @inlinable + static func encrypt( + _ plaintext: Plaintext, + using key: SymmetricKey, + nonce: AES._CTR.Nonce + ) throws -> Data { + guard [128, 192, 256].contains(key.bitCount) else { + throw CryptoKitError.incorrectKeySize + } + return plaintext.withUnsafeBytes { plaintextBufferPtr in + Self._encrypt(plaintextBufferPtr, using: key, nonce: nonce) + } + } + + @usableFromInline + static func _encrypt( + _ plaintextBufferPtr: UnsafeRawBufferPointer, + using key: SymmetricKey, + nonce: AES._CTR.Nonce + ) -> Data { + var ciphertext = Data(repeating: 0, count: plaintextBufferPtr.count) + ciphertext.withUnsafeMutableBytes { ciphertextBufferPtr in + var nonce = nonce + var ecountBytes = (Int64.zero, Int64.zero) + var num = UInt32.zero + key.withUnsafeBytes { keyBufferPtr in + nonce.withUnsafeMutableBytes { nonceBufferPtr in + withUnsafeMutableBytes(of: &ecountBytes) { ecountBufferPtr in + var key = AES_KEY() + precondition(CCryptoBoringSSL_AES_set_encrypt_key(keyBufferPtr.baseAddress, UInt32(keyBufferPtr.count * 8), &key) == 0) + CCryptoBoringSSL_AES_ctr128_encrypt( + plaintextBufferPtr.baseAddress, + ciphertextBufferPtr.baseAddress, + plaintextBufferPtr.count, + &key, + nonceBufferPtr.baseAddress, + ecountBufferPtr.baseAddress, + &num + ) + } + } + } + } + return ciphertext + } +} diff --git a/Tests/_CryptoExtrasTests/AES_CTRTests.swift b/Tests/_CryptoExtrasTests/AES_CTRTests.swift index 83e5c6fd..5083bd8b 100644 --- a/Tests/_CryptoExtrasTests/AES_CTRTests.swift +++ b/Tests/_CryptoExtrasTests/AES_CTRTests.swift @@ -156,65 +156,4 @@ final class AESCTRTests: XCTestCase { } } } - - func testOverflowAllNonceBytesWorks() throws { - var nonce = try AES._CTR.Nonce(nonceBytes: repeatElement(0xFF, count: 16)) - nonce.withUnsafeMutableBytes { nonceBytes in - XCTAssertTrue(nonceBytes.allSatisfy { $0 == 0xFF }) - } - - nonce.incrementCounter() - - nonce.withUnsafeMutableBytes { nonceBytes in - XCTAssertTrue(nonceBytes.allSatisfy { $0 == 0x00 }) - } - } - - func testOverflowCounterCorrectlyStoresInBigEndian() throws { - let nonceBytes: [UInt8] = [ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x04, - 0xFF, 0xFF, 0xFF, 0xFF, - ] - var nonce = try AES._CTR.Nonce(nonceBytes: nonceBytes) - - nonce.incrementCounter() - - let newNonceBytes = nonce.withUnsafeMutableBytes { - Array($0) - } - - let expectedNonceBytes: [UInt8] = [ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x05, - 0x00, 0x00, 0x00, 0x00, - ] - XCTAssertEqual(expectedNonceBytes, newNonceBytes) - } - - func testOverflowCounterAndNextUInt32CorrectlyStoresInBigEndian() throws { - let nonceBytes: [UInt8] = [ - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, - ] - var nonce = try AES._CTR.Nonce(nonceBytes: nonceBytes) - - nonce.incrementCounter() - - let newNonceBytes = nonce.withUnsafeMutableBytes { - Array($0) - } - - let expectedNonceBytes: [UInt8] = [ - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x09, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ] - XCTAssertEqual(expectedNonceBytes, newNonceBytes) - } }