Skip to content

Commit

Permalink
Make ChaCha20 inherit from Salsa20.
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerxaic committed Apr 27, 2019
1 parent dc03424 commit 43bd963
Showing 1 changed file with 4 additions and 175 deletions.
179 changes: 4 additions & 175 deletions Sources/CryptoSwift/ChaCha20.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// CryptoSwift
//
// Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin@krzyzanowskim.com>
// Copyright (C) 2019 Roger Miret <roger.miret@gmail.com>
// Copyright (C) 2019 Roger Miret Giné <roger.miret@gmail.com>
// This software is provided 'as-is', without any express or implied warranty.
//
// In no event will the authors be held liable for any damages arising from the use of this software.
Expand All @@ -17,42 +17,9 @@
// https://tools.ietf.org/html/rfc7539
//

public final class ChaCha20: BlockCipher {
public enum Error: Swift.Error {
case invalidKeyOrInitializationVector
case notSupported
}

public static let blockSize = 64 // 512 / 8
public let keySize: Int

fileprivate let key: Key
fileprivate var counter: Array<UInt8>

public init(key: Array<UInt8>, iv nonce: Array<UInt8>) throws {
precondition(nonce.count == 12 || nonce.count == 8)
public final class ChaCha20: Salsa20 {

if key.count != 32 {
throw Error.invalidKeyOrInitializationVector
}

self.key = Key(bytes: key)
keySize = self.key.count

if nonce.count == 8 {
counter = [0, 0, 0, 0, 0, 0, 0, 0] + nonce
} else {
counter = [0, 0, 0, 0] + nonce
}

assert(counter.count == 16)
}

fileprivate func rotl(_ a: UInt32, _ b: Int) -> UInt32 {
return (a << b) | (a >> (32 - b))
}

fileprivate func qr(_ a: inout UInt32, _ b: inout UInt32, _ c: inout UInt32, _ d: inout UInt32) {
override internal func qr(_ a: inout UInt32, _ b: inout UInt32, _ c: inout UInt32, _ d: inout UInt32) {
a = a &+ b
d ^= a
d = rotl(d, 16)
Expand All @@ -71,7 +38,7 @@ public final class ChaCha20: BlockCipher {
}

/// https://tools.ietf.org/html/rfc7539#section-2.3.
fileprivate func core(block: inout Array<UInt8>, counter: Array<UInt8>, key: Array<UInt8>) {
override internal func core(block: inout Array<UInt8>, counter: Array<UInt8>, key: Array<UInt8>) {
precondition(block.count == ChaCha20.blockSize)
precondition(counter.count == 16)
precondition(key.count == 32)
Expand Down Expand Up @@ -143,142 +110,4 @@ public final class ChaCha20: BlockCipher {
block.replaceSubrange(56..<60, with: x14.bigEndian.bytes())
block.replaceSubrange(60..<64, with: x15.bigEndian.bytes())
}

// XORKeyStream
func process(bytes: ArraySlice<UInt8>, counter: inout Array<UInt8>, key: Array<UInt8>) -> Array<UInt8> {
precondition(counter.count == 16)
precondition(key.count == 32)

var block = Array<UInt8>(repeating: 0, count: ChaCha20.blockSize)
var bytesSlice = bytes
var out = Array<UInt8>(reserveCapacity: bytesSlice.count)

while bytesSlice.count >= ChaCha20.blockSize {
core(block: &block, counter: counter, key: key)
for (i, x) in block.enumerated() {
out.append(bytesSlice[bytesSlice.startIndex + i] ^ x)
}
var u: UInt32 = 1
for i in 0..<4 {
u += UInt32(counter[i])
counter[i] = UInt8(u & 0xff)
u >>= 8
}
bytesSlice = bytesSlice[bytesSlice.startIndex + ChaCha20.blockSize..<bytesSlice.endIndex]
}

if bytesSlice.count > 0 {
core(block: &block, counter: counter, key: key)
for (i, v) in bytesSlice.enumerated() {
out.append(v ^ block[i])
}
}
return out
}
}

// MARK: Cipher

extension ChaCha20: Cipher {
public func encrypt(_ bytes: ArraySlice<UInt8>) throws -> Array<UInt8> {
return process(bytes: bytes, counter: &counter, key: Array(key))
}

public func decrypt(_ bytes: ArraySlice<UInt8>) throws -> Array<UInt8> {
return try encrypt(bytes)
}
}

// MARK: Encryptor

extension ChaCha20 {
public struct ChaChaEncryptor: Cryptor, Updatable {
private var accumulated = Array<UInt8>()
private let chacha: ChaCha20

init(chacha: ChaCha20) {
self.chacha = chacha
}

public mutating func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = false) throws -> Array<UInt8> {
accumulated += bytes

var encrypted = Array<UInt8>()
encrypted.reserveCapacity(accumulated.count)
for chunk in accumulated.batched(by: ChaCha20.blockSize) {
if isLast || accumulated.count >= ChaCha20.blockSize {
encrypted += try chacha.encrypt(chunk)
accumulated.removeFirst(chunk.count) // TODO: improve performance
}
}
return encrypted
}

public func seek(to: Int) throws {
throw Error.notSupported
}
}
}

// MARK: Decryptor

extension ChaCha20 {
public struct ChaChaDecryptor: Cryptor, Updatable {
private var accumulated = Array<UInt8>()

private var offset: Int = 0
private var offsetToRemove: Int = 0
private let chacha: ChaCha20

init(chacha: ChaCha20) {
self.chacha = chacha
}

public mutating func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = true) throws -> Array<UInt8> {
// prepend "offset" number of bytes at the beginning
if offset > 0 {
accumulated += Array<UInt8>(repeating: 0, count: offset) + bytes
offsetToRemove = offset
offset = 0
} else {
accumulated += bytes
}

var plaintext = Array<UInt8>()
plaintext.reserveCapacity(accumulated.count)
for chunk in accumulated.batched(by: ChaCha20.blockSize) {
if isLast || accumulated.count >= ChaCha20.blockSize {
plaintext += try chacha.decrypt(chunk)

// remove "offset" from the beginning of first chunk
if offsetToRemove > 0 {
plaintext.removeFirst(offsetToRemove) // TODO: improve performance
offsetToRemove = 0
}

accumulated.removeFirst(chunk.count)
}
}

return plaintext
}

public func seek(to: Int) throws {
throw Error.notSupported
}
}
}

// MARK: Cryptors

extension ChaCha20: Cryptors {
//TODO: Use BlockEncryptor/BlockDecryptor

public func makeEncryptor() -> Cryptor & Updatable {
return ChaCha20.ChaChaEncryptor(chacha: self)
}

public func makeDecryptor() -> Cryptor & Updatable {
return ChaCha20.ChaChaDecryptor(chacha: self)
}
}

0 comments on commit 43bd963

Please sign in to comment.