Skip to content

Commit

Permalink
Use BoringSSL implementation for AES_CTR (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonjbeaumont authored May 7, 2024
1 parent 2adec61 commit bc1c292
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 125 deletions.
74 changes: 10 additions & 64 deletions Sources/_CryptoExtras/AES/AES_CTR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<AES._CTR.Nonce>.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<AES._CTR.Nonce>.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..<min(remainingPlaintextBytes, MemoryLayout<AES._CTR.Nonce>.size) {
plaintext[offset &+ i] ^= noncePtr[i]
}
}

nonce.incrementCounter()
}
}
extension AES {

public enum _CTR {
@inlinable
public static func encrypt<Plaintext: DataProtocol>(
_ 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: DataProtocol>(
_ 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)
}
}
}
Expand Down Expand Up @@ -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<ReturnType>(_ body: (UnsafeMutableRawBufferPointer) throws -> ReturnType) rethrows -> ReturnType {
return try Swift.withUnsafeMutableBytes(of: &self.nonceBytes, body)
}
Expand Down
66 changes: 66 additions & 0 deletions Sources/_CryptoExtras/AES/BoringSSL/AES_CTR_boring.swift
Original file line number Diff line number Diff line change
@@ -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: ContiguousBytes>(
_ 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
}
}
61 changes: 0 additions & 61 deletions Tests/_CryptoExtrasTests/AES_CTRTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit bc1c292

Please sign in to comment.