|
| 1 | +// |
| 2 | +// CryptoSwift |
| 3 | +// |
| 4 | +// Copyright (C) 2014-2017 Marcin Krzyżanowski < [email protected]> |
| 5 | +// Copyright (C) 2019 Roger Miret Giné < [email protected]> |
| 6 | +// This software is provided 'as-is', without any express or implied warranty. |
| 7 | +// |
| 8 | +// In no event will the authors be held liable for any damages arising from the use of this software. |
| 9 | +// |
| 10 | +// Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: |
| 11 | +// |
| 12 | +// - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required. |
| 13 | +// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. |
| 14 | +// - This notice may not be removed or altered from any source or binary distribution. |
| 15 | +// |
| 16 | + |
| 17 | +public class Salsa20: BlockCipher { |
| 18 | + public enum Error: Swift.Error { |
| 19 | + case invalidKeyOrInitializationVector |
| 20 | + case notSupported |
| 21 | + } |
| 22 | + |
| 23 | + public static let blockSize = 64 // 512 / 8 |
| 24 | + public let keySize: Int |
| 25 | + |
| 26 | + let key: Key |
| 27 | + var counter: Array<UInt8> |
| 28 | + |
| 29 | + public init(key: Array<UInt8>, iv nonce: Array<UInt8>) throws { |
| 30 | + precondition(nonce.count == 12 || nonce.count == 8) |
| 31 | + |
| 32 | + if key.count != 32 { |
| 33 | + throw Error.invalidKeyOrInitializationVector |
| 34 | + } |
| 35 | + |
| 36 | + self.key = Key(bytes: key) |
| 37 | + keySize = self.key.count |
| 38 | + |
| 39 | + if nonce.count == 8 { |
| 40 | + counter = [0, 0, 0, 0, 0, 0, 0, 0] + nonce |
| 41 | + } else { |
| 42 | + counter = [0, 0, 0, 0] + nonce |
| 43 | + } |
| 44 | + |
| 45 | + assert(counter.count == 16) |
| 46 | + } |
| 47 | + |
| 48 | + func qr(_ a: inout UInt32, _ b: inout UInt32, _ c: inout UInt32, _ d: inout UInt32) { |
| 49 | + b ^= rotl(a + d, 7) |
| 50 | + c ^= rotl(b + a, 9) |
| 51 | + d ^= rotl(c + b, 13) |
| 52 | + a ^= rotl(d + c, 18) |
| 53 | + } |
| 54 | + |
| 55 | + func core(block: inout Array<UInt8>, counter: Array<UInt8>, key: Array<UInt8>) { |
| 56 | + precondition(block.count == Salsa20.blockSize) |
| 57 | + precondition(counter.count == 16) |
| 58 | + precondition(key.count == 32) |
| 59 | + |
| 60 | + let j0: UInt32 = 0x61707865 |
| 61 | + let j1: UInt32 = UInt32(bytes: key[0..<4]).bigEndian |
| 62 | + let j2: UInt32 = UInt32(bytes: key[4..<8]).bigEndian |
| 63 | + let j3: UInt32 = UInt32(bytes: key[8..<12]).bigEndian |
| 64 | + let j4: UInt32 = UInt32(bytes: key[12..<16]).bigEndian |
| 65 | + let j5: UInt32 = 0x3320646e // 0x3620646e sigma/tau |
| 66 | + let j6: UInt32 = UInt32(bytes: counter[8..<12]).bigEndian |
| 67 | + let j7: UInt32 = UInt32(bytes: counter[12..<16]).bigEndian |
| 68 | + let j8: UInt32 = UInt32(bytes: counter[0..<4]).bigEndian |
| 69 | + let j9: UInt32 = UInt32(bytes: counter[4..<8]).bigEndian |
| 70 | + let j10: UInt32 = 0x79622d32 |
| 71 | + let j11: UInt32 = UInt32(bytes: key[16..<20]).bigEndian |
| 72 | + let j12: UInt32 = UInt32(bytes: key[20..<24]).bigEndian |
| 73 | + let j13: UInt32 = UInt32(bytes: key[24..<28]).bigEndian |
| 74 | + let j14: UInt32 = UInt32(bytes: key[28..<32]).bigEndian |
| 75 | + let j15: UInt32 = 0x6b206574 |
| 76 | + |
| 77 | + var (x0, x1, x2, x3, x4, x5, x6, x7) = (j0, j1, j2, j3, j4, j5, j6, j7) |
| 78 | + var (x8, x9, x10, x11, x12, x13, x14, x15) = (j8, j9, j10, j11, j12, j13, j14, j15) |
| 79 | + |
| 80 | + for _ in 0..<10 { // 20 rounds |
| 81 | + // Odd round |
| 82 | + qr(&x0, &x4, &x8, &x12) // column 1 |
| 83 | + qr(&x5, &x9, &x13, &x1) // column 2 |
| 84 | + qr(&x10, &x14, &x2, &x6) // column 3 |
| 85 | + qr(&x15, &x3, &x7, &x11) // column 4 |
| 86 | + |
| 87 | + // Even round |
| 88 | + qr(&x0, &x1, &x2, &x3) // row 1 |
| 89 | + qr(&x5, &x6, &x7, &x4) // row 2 |
| 90 | + qr(&x10, &x11, &x8, &x9) // row 3 |
| 91 | + qr(&x15, &x12, &x13, &x14) // row 4 |
| 92 | + } |
| 93 | + |
| 94 | + x0 = x0 &+ j0 |
| 95 | + x1 = x1 &+ j1 |
| 96 | + x2 = x2 &+ j2 |
| 97 | + x3 = x3 &+ j3 |
| 98 | + x4 = x4 &+ j4 |
| 99 | + x5 = x5 &+ j5 |
| 100 | + x6 = x6 &+ j6 |
| 101 | + x7 = x7 &+ j7 |
| 102 | + x8 = x8 &+ j8 |
| 103 | + x9 = x9 &+ j9 |
| 104 | + x10 = x10 &+ j10 |
| 105 | + x11 = x11 &+ j11 |
| 106 | + x12 = x12 &+ j12 |
| 107 | + x13 = x13 &+ j13 |
| 108 | + x14 = x14 &+ j14 |
| 109 | + x15 = x15 &+ j15 |
| 110 | + |
| 111 | + block.replaceSubrange(0..<4, with: x0.bigEndian.bytes()) |
| 112 | + block.replaceSubrange(4..<8, with: x1.bigEndian.bytes()) |
| 113 | + block.replaceSubrange(8..<12, with: x2.bigEndian.bytes()) |
| 114 | + block.replaceSubrange(12..<16, with: x3.bigEndian.bytes()) |
| 115 | + block.replaceSubrange(16..<20, with: x4.bigEndian.bytes()) |
| 116 | + block.replaceSubrange(20..<24, with: x5.bigEndian.bytes()) |
| 117 | + block.replaceSubrange(24..<28, with: x6.bigEndian.bytes()) |
| 118 | + block.replaceSubrange(28..<32, with: x7.bigEndian.bytes()) |
| 119 | + block.replaceSubrange(32..<36, with: x8.bigEndian.bytes()) |
| 120 | + block.replaceSubrange(36..<40, with: x9.bigEndian.bytes()) |
| 121 | + block.replaceSubrange(40..<44, with: x10.bigEndian.bytes()) |
| 122 | + block.replaceSubrange(44..<48, with: x11.bigEndian.bytes()) |
| 123 | + block.replaceSubrange(48..<52, with: x12.bigEndian.bytes()) |
| 124 | + block.replaceSubrange(52..<56, with: x13.bigEndian.bytes()) |
| 125 | + block.replaceSubrange(56..<60, with: x14.bigEndian.bytes()) |
| 126 | + block.replaceSubrange(60..<64, with: x15.bigEndian.bytes()) |
| 127 | + } |
| 128 | + |
| 129 | + // XORKeyStream |
| 130 | + func process(bytes: ArraySlice<UInt8>, counter: inout Array<UInt8>, key: Array<UInt8>) -> Array<UInt8> { |
| 131 | + precondition(counter.count == 16) |
| 132 | + precondition(key.count == 32) |
| 133 | + |
| 134 | + var block = Array<UInt8>(repeating: 0, count: Salsa20.blockSize) |
| 135 | + var bytesSlice = bytes |
| 136 | + var out = Array<UInt8>(reserveCapacity: bytesSlice.count) |
| 137 | + |
| 138 | + while bytesSlice.count >= Salsa20.blockSize { |
| 139 | + core(block: &block, counter: counter, key: key) |
| 140 | + for (i, x) in block.enumerated() { |
| 141 | + out.append(bytesSlice[bytesSlice.startIndex + i] ^ x) |
| 142 | + } |
| 143 | + var u: UInt32 = 1 |
| 144 | + for i in 0..<4 { |
| 145 | + u += UInt32(counter[i]) |
| 146 | + counter[i] = UInt8(u & 0xff) |
| 147 | + u >>= 8 |
| 148 | + } |
| 149 | + bytesSlice = bytesSlice[bytesSlice.startIndex + Salsa20.blockSize..<bytesSlice.endIndex] |
| 150 | + } |
| 151 | + |
| 152 | + if bytesSlice.count > 0 { |
| 153 | + core(block: &block, counter: counter, key: key) |
| 154 | + for (i, v) in bytesSlice.enumerated() { |
| 155 | + out.append(v ^ block[i]) |
| 156 | + } |
| 157 | + } |
| 158 | + return out |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +// MARK: Cipher |
| 163 | + |
| 164 | +extension Salsa20: Cipher { |
| 165 | + public func encrypt(_ bytes: ArraySlice<UInt8>) throws -> Array<UInt8> { |
| 166 | + return process(bytes: bytes, counter: &counter, key: Array(key)) |
| 167 | + } |
| 168 | + |
| 169 | + public func decrypt(_ bytes: ArraySlice<UInt8>) throws -> Array<UInt8> { |
| 170 | + return try encrypt(bytes) |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +// MARK: Encryptor |
| 175 | + |
| 176 | +extension Salsa20 { |
| 177 | + public struct SalsaEncryptor: Cryptor, Updatable { |
| 178 | + private var accumulated = Array<UInt8>() |
| 179 | + private let salsa: Salsa20 |
| 180 | + |
| 181 | + init(salsa: Salsa20) { |
| 182 | + self.salsa = salsa |
| 183 | + } |
| 184 | + |
| 185 | + public mutating func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = false) throws -> Array<UInt8> { |
| 186 | + accumulated += bytes |
| 187 | + |
| 188 | + var encrypted = Array<UInt8>() |
| 189 | + encrypted.reserveCapacity(accumulated.count) |
| 190 | + for chunk in accumulated.batched(by: Salsa20.blockSize) { |
| 191 | + if isLast || accumulated.count >= Salsa20.blockSize { |
| 192 | + encrypted += try salsa.encrypt(chunk) |
| 193 | + accumulated.removeFirst(chunk.count) // TODO: improve performance |
| 194 | + } |
| 195 | + } |
| 196 | + return encrypted |
| 197 | + } |
| 198 | + |
| 199 | + public func seek(to: Int) throws { |
| 200 | + throw Error.notSupported |
| 201 | + } |
| 202 | + } |
| 203 | +} |
| 204 | + |
| 205 | +// MARK: Decryptor |
| 206 | + |
| 207 | +extension Salsa20 { |
| 208 | + public struct SalsaDecryptor: Cryptor, Updatable { |
| 209 | + private var accumulated = Array<UInt8>() |
| 210 | + |
| 211 | + private var offset: Int = 0 |
| 212 | + private var offsetToRemove: Int = 0 |
| 213 | + private let salsa: Salsa20 |
| 214 | + |
| 215 | + init(salsa: Salsa20) { |
| 216 | + self.salsa = salsa |
| 217 | + } |
| 218 | + |
| 219 | + public mutating func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = true) throws -> Array<UInt8> { |
| 220 | + // prepend "offset" number of bytes at the beginning |
| 221 | + if offset > 0 { |
| 222 | + accumulated += Array<UInt8>(repeating: 0, count: offset) + bytes |
| 223 | + offsetToRemove = offset |
| 224 | + offset = 0 |
| 225 | + } else { |
| 226 | + accumulated += bytes |
| 227 | + } |
| 228 | + |
| 229 | + var plaintext = Array<UInt8>() |
| 230 | + plaintext.reserveCapacity(accumulated.count) |
| 231 | + for chunk in accumulated.batched(by: Salsa20.blockSize) { |
| 232 | + if isLast || accumulated.count >= Salsa20.blockSize { |
| 233 | + plaintext += try salsa.decrypt(chunk) |
| 234 | + |
| 235 | + // remove "offset" from the beginning of first chunk |
| 236 | + if offsetToRemove > 0 { |
| 237 | + plaintext.removeFirst(offsetToRemove) // TODO: improve performance |
| 238 | + offsetToRemove = 0 |
| 239 | + } |
| 240 | + |
| 241 | + accumulated.removeFirst(chunk.count) |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + return plaintext |
| 246 | + } |
| 247 | + |
| 248 | + public func seek(to: Int) throws { |
| 249 | + throw Error.notSupported |
| 250 | + } |
| 251 | + } |
| 252 | +} |
| 253 | + |
| 254 | +// MARK: Cryptors |
| 255 | + |
| 256 | +extension Salsa20: Cryptors { |
| 257 | + //TODO: Use BlockEncryptor/BlockDecryptor |
| 258 | + |
| 259 | + public func makeEncryptor() -> Cryptor & Updatable { |
| 260 | + return Salsa20.SalsaEncryptor(salsa: self) |
| 261 | + } |
| 262 | + |
| 263 | + public func makeDecryptor() -> Cryptor & Updatable { |
| 264 | + return Salsa20.SalsaDecryptor(salsa: self) |
| 265 | + } |
| 266 | +} |
0 commit comments