Skip to content

Commit dc03424

Browse files
committed
Added Salsa20 Cipher with tests.
1 parent 0cafa39 commit dc03424

File tree

6 files changed

+736
-0
lines changed

6 files changed

+736
-0
lines changed

CryptoSwift.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
E3FD2D531D6B81CE00A9F35F /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */; };
126126
E6200E141FB9A7AE00258382 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6200E131FB9A7AE00258382 /* HKDF.swift */; };
127127
E6200E171FB9B68C00258382 /* HKDFTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6200E151FB9B67C00258382 /* HKDFTests.swift */; };
128+
EBB32ABF22737C16003A065D /* Salsa20Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB51933F2268D600009CE32B /* Salsa20Tests.swift */; };
129+
EBCE52E7226515AD00D8DAE7 /* Salsa20.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCE52E6226515AD00D8DAE7 /* Salsa20.swift */; };
128130
/* End PBXBuildFile section */
129131

130132
/* Begin PBXContainerItemProxy section */
@@ -373,6 +375,8 @@
373375
E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Error+Extension.swift"; sourceTree = "<group>"; };
374376
E6200E131FB9A7AE00258382 /* HKDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKDF.swift; sourceTree = "<group>"; };
375377
E6200E151FB9B67C00258382 /* HKDFTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKDFTests.swift; sourceTree = "<group>"; };
378+
EB51933F2268D600009CE32B /* Salsa20Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Salsa20Tests.swift; sourceTree = "<group>"; };
379+
EBCE52E6226515AD00D8DAE7 /* Salsa20.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Salsa20.swift; sourceTree = "<group>"; };
376380
/* End PBXFileReference section */
377381

378382
/* Begin PBXFrameworksBuildPhase section */
@@ -500,6 +504,7 @@
500504
75C2E76C1D55F097003D2BCA /* Access.swift */,
501505
756BFDCA1A82B87300B9D9A4 /* Bridging.h */,
502506
754BE46519693E190098E6F3 /* Supporting Files */,
507+
EB51933F2268D600009CE32B /* Salsa20Tests.swift */,
503508
);
504509
name = Tests;
505510
path = Tests/Tests;
@@ -615,6 +620,7 @@
615620
75B3ED78210FA016005D4ADA /* BlockEncryptor.swift */,
616621
753674062175D012003E32A6 /* StreamDecryptor.swift */,
617622
756A64C52111083B00BE8805 /* StreamEncryptor.swift */,
623+
EBCE52E6226515AD00D8DAE7 /* Salsa20.swift */,
618624
);
619625
path = CryptoSwift;
620626
sourceTree = "<group>";
@@ -925,6 +931,7 @@
925931
75EC52841EE8B8170048EB3B /* CipherModeWorker.swift in Sources */,
926932
75EC52A41EE8B8290048EB3B /* Operators.swift in Sources */,
927933
75EC529A1EE8B8200048EB3B /* HMAC+Foundation.swift in Sources */,
934+
EBCE52E7226515AD00D8DAE7 /* Salsa20.swift in Sources */,
928935
75EC52B21EE8B83D0048EB3B /* String+Extension.swift in Sources */,
929936
750509991F6BEF2A00394A1B /* PKCS7.swift in Sources */,
930937
75EC52B51EE8B83D0048EB3B /* UInt64+Extension.swift in Sources */,
@@ -978,6 +985,7 @@
978985
758A94291A65C67400E46135 /* HMACTests.swift in Sources */,
979986
75100F8F19B0BC890005C5F5 /* Poly1305Tests.swift in Sources */,
980987
E6200E171FB9B68C00258382 /* HKDFTests.swift in Sources */,
988+
EBB32ABF22737C16003A065D /* Salsa20Tests.swift in Sources */,
981989
753B33011DAB84D600D06422 /* RandomBytesSequenceTests.swift in Sources */,
982990
754BE46819693E190098E6F3 /* DigestTests.swift in Sources */,
983991
E3FD2D531D6B81CE00A9F35F /* Error+Extension.swift in Sources */,

CryptoSwift.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
<BuildAction
66
parallelizeBuildables = "NO"
77
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForRunning = "YES"
11+
buildForTesting = "YES">
12+
<BuildableReference
13+
BuildableIdentifier = "primary"
14+
BlueprintIdentifier = "754BE45F19693E190098E6F3"
15+
BuildableName = "Tests.xctest"
16+
BlueprintName = "Tests"
17+
ReferencedContainer = "container:CryptoSwift.xcodeproj">
18+
</BuildableReference>
19+
</BuildActionEntry>
20+
</BuildActionEntries>
821
</BuildAction>
922
<TestAction
1023
buildConfiguration = "Test"

CryptoSwift.xcodeproj/xcshareddata/xcschemes/TestsPerformance-Mac.xcscheme

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
<BuildAction
66
parallelizeBuildables = "NO"
77
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForRunning = "YES"
11+
buildForTesting = "YES">
12+
<BuildableReference
13+
BuildableIdentifier = "primary"
14+
BlueprintIdentifier = "7595C1492072E48C00EA1A5F"
15+
BuildableName = "TestsPerformance-Mac.xctest"
16+
BlueprintName = "TestsPerformance-Mac"
17+
ReferencedContainer = "container:CryptoSwift.xcodeproj">
18+
</BuildableReference>
19+
</BuildActionEntry>
20+
</BuildActionEntries>
821
</BuildAction>
922
<TestAction
1023
buildConfiguration = "Test"

CryptoSwift.xcodeproj/xcshareddata/xcschemes/TestsPerformance-iOS.xcscheme

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
<BuildAction
66
parallelizeBuildables = "NO"
77
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForRunning = "YES"
11+
buildForTesting = "YES">
12+
<BuildableReference
13+
BuildableIdentifier = "primary"
14+
BlueprintIdentifier = "7564F04E2072EAEB00CA5A96"
15+
BuildableName = "TestsPerformance-iOS.xctest"
16+
BlueprintName = "TestsPerformance-iOS"
17+
ReferencedContainer = "container:CryptoSwift.xcodeproj">
18+
</BuildableReference>
19+
</BuildActionEntry>
20+
</BuildActionEntries>
821
</BuildAction>
922
<TestAction
1023
buildConfiguration = "Test"

Sources/CryptoSwift/Salsa20.swift

+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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

Comments
 (0)