Skip to content

Commit

Permalink
Added Salsa20 Cipher with tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
rogerxaic committed Apr 27, 2019
1 parent 0cafa39 commit dc03424
Show file tree
Hide file tree
Showing 6 changed files with 736 additions and 0 deletions.
8 changes: 8 additions & 0 deletions CryptoSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
E3FD2D531D6B81CE00A9F35F /* Error+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */; };
E6200E141FB9A7AE00258382 /* HKDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6200E131FB9A7AE00258382 /* HKDF.swift */; };
E6200E171FB9B68C00258382 /* HKDFTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6200E151FB9B67C00258382 /* HKDFTests.swift */; };
EBB32ABF22737C16003A065D /* Salsa20Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB51933F2268D600009CE32B /* Salsa20Tests.swift */; };
EBCE52E7226515AD00D8DAE7 /* Salsa20.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCE52E6226515AD00D8DAE7 /* Salsa20.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -373,6 +375,8 @@
E3FD2D511D6B813C00A9F35F /* Error+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Error+Extension.swift"; sourceTree = "<group>"; };
E6200E131FB9A7AE00258382 /* HKDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKDF.swift; sourceTree = "<group>"; };
E6200E151FB9B67C00258382 /* HKDFTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKDFTests.swift; sourceTree = "<group>"; };
EB51933F2268D600009CE32B /* Salsa20Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Salsa20Tests.swift; sourceTree = "<group>"; };
EBCE52E6226515AD00D8DAE7 /* Salsa20.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Salsa20.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -500,6 +504,7 @@
75C2E76C1D55F097003D2BCA /* Access.swift */,
756BFDCA1A82B87300B9D9A4 /* Bridging.h */,
754BE46519693E190098E6F3 /* Supporting Files */,
EB51933F2268D600009CE32B /* Salsa20Tests.swift */,
);
name = Tests;
path = Tests/Tests;
Expand Down Expand Up @@ -615,6 +620,7 @@
75B3ED78210FA016005D4ADA /* BlockEncryptor.swift */,
753674062175D012003E32A6 /* StreamDecryptor.swift */,
756A64C52111083B00BE8805 /* StreamEncryptor.swift */,
EBCE52E6226515AD00D8DAE7 /* Salsa20.swift */,
);
path = CryptoSwift;
sourceTree = "<group>";
Expand Down Expand Up @@ -925,6 +931,7 @@
75EC52841EE8B8170048EB3B /* CipherModeWorker.swift in Sources */,
75EC52A41EE8B8290048EB3B /* Operators.swift in Sources */,
75EC529A1EE8B8200048EB3B /* HMAC+Foundation.swift in Sources */,
EBCE52E7226515AD00D8DAE7 /* Salsa20.swift in Sources */,
75EC52B21EE8B83D0048EB3B /* String+Extension.swift in Sources */,
750509991F6BEF2A00394A1B /* PKCS7.swift in Sources */,
75EC52B51EE8B83D0048EB3B /* UInt64+Extension.swift in Sources */,
Expand Down Expand Up @@ -978,6 +985,7 @@
758A94291A65C67400E46135 /* HMACTests.swift in Sources */,
75100F8F19B0BC890005C5F5 /* Poly1305Tests.swift in Sources */,
E6200E171FB9B68C00258382 /* HKDFTests.swift in Sources */,
EBB32ABF22737C16003A065D /* Salsa20Tests.swift in Sources */,
753B33011DAB84D600D06422 /* RandomBytesSequenceTests.swift in Sources */,
754BE46819693E190098E6F3 /* DigestTests.swift in Sources */,
E3FD2D531D6B81CE00A9F35F /* Error+Extension.swift in Sources */,
Expand Down
13 changes: 13 additions & 0 deletions CryptoSwift.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForRunning = "YES"
buildForTesting = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "754BE45F19693E190098E6F3"
BuildableName = "Tests.xctest"
BlueprintName = "Tests"
ReferencedContainer = "container:CryptoSwift.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForRunning = "YES"
buildForTesting = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7595C1492072E48C00EA1A5F"
BuildableName = "TestsPerformance-Mac.xctest"
BlueprintName = "TestsPerformance-Mac"
ReferencedContainer = "container:CryptoSwift.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForRunning = "YES"
buildForTesting = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "7564F04E2072EAEB00CA5A96"
BuildableName = "TestsPerformance-iOS.xctest"
BlueprintName = "TestsPerformance-iOS"
ReferencedContainer = "container:CryptoSwift.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Test"
Expand Down
266 changes: 266 additions & 0 deletions Sources/CryptoSwift/Salsa20.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
//
// CryptoSwift
//
// Copyright (C) 2014-2017 Marcin Krzyżanowski <[email protected]>
// Copyright (C) 2019 Roger Miret Giné <[email protected]>
// 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.
//
// 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:
//
// - 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.
// - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
// - This notice may not be removed or altered from any source or binary distribution.
//

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

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

let key: Key
var counter: Array<UInt8>

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

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)
}

func qr(_ a: inout UInt32, _ b: inout UInt32, _ c: inout UInt32, _ d: inout UInt32) {
b ^= rotl(a + d, 7)
c ^= rotl(b + a, 9)
d ^= rotl(c + b, 13)
a ^= rotl(d + c, 18)
}

func core(block: inout Array<UInt8>, counter: Array<UInt8>, key: Array<UInt8>) {
precondition(block.count == Salsa20.blockSize)
precondition(counter.count == 16)
precondition(key.count == 32)

let j0: UInt32 = 0x61707865
let j1: UInt32 = UInt32(bytes: key[0..<4]).bigEndian
let j2: UInt32 = UInt32(bytes: key[4..<8]).bigEndian
let j3: UInt32 = UInt32(bytes: key[8..<12]).bigEndian
let j4: UInt32 = UInt32(bytes: key[12..<16]).bigEndian
let j5: UInt32 = 0x3320646e // 0x3620646e sigma/tau
let j6: UInt32 = UInt32(bytes: counter[8..<12]).bigEndian
let j7: UInt32 = UInt32(bytes: counter[12..<16]).bigEndian
let j8: UInt32 = UInt32(bytes: counter[0..<4]).bigEndian
let j9: UInt32 = UInt32(bytes: counter[4..<8]).bigEndian
let j10: UInt32 = 0x79622d32
let j11: UInt32 = UInt32(bytes: key[16..<20]).bigEndian
let j12: UInt32 = UInt32(bytes: key[20..<24]).bigEndian
let j13: UInt32 = UInt32(bytes: key[24..<28]).bigEndian
let j14: UInt32 = UInt32(bytes: key[28..<32]).bigEndian
let j15: UInt32 = 0x6b206574

var (x0, x1, x2, x3, x4, x5, x6, x7) = (j0, j1, j2, j3, j4, j5, j6, j7)
var (x8, x9, x10, x11, x12, x13, x14, x15) = (j8, j9, j10, j11, j12, j13, j14, j15)

for _ in 0..<10 { // 20 rounds
// Odd round
qr(&x0, &x4, &x8, &x12) // column 1
qr(&x5, &x9, &x13, &x1) // column 2
qr(&x10, &x14, &x2, &x6) // column 3
qr(&x15, &x3, &x7, &x11) // column 4

// Even round
qr(&x0, &x1, &x2, &x3) // row 1
qr(&x5, &x6, &x7, &x4) // row 2
qr(&x10, &x11, &x8, &x9) // row 3
qr(&x15, &x12, &x13, &x14) // row 4
}

x0 = x0 &+ j0
x1 = x1 &+ j1
x2 = x2 &+ j2
x3 = x3 &+ j3
x4 = x4 &+ j4
x5 = x5 &+ j5
x6 = x6 &+ j6
x7 = x7 &+ j7
x8 = x8 &+ j8
x9 = x9 &+ j9
x10 = x10 &+ j10
x11 = x11 &+ j11
x12 = x12 &+ j12
x13 = x13 &+ j13
x14 = x14 &+ j14
x15 = x15 &+ j15

block.replaceSubrange(0..<4, with: x0.bigEndian.bytes())
block.replaceSubrange(4..<8, with: x1.bigEndian.bytes())
block.replaceSubrange(8..<12, with: x2.bigEndian.bytes())
block.replaceSubrange(12..<16, with: x3.bigEndian.bytes())
block.replaceSubrange(16..<20, with: x4.bigEndian.bytes())
block.replaceSubrange(20..<24, with: x5.bigEndian.bytes())
block.replaceSubrange(24..<28, with: x6.bigEndian.bytes())
block.replaceSubrange(28..<32, with: x7.bigEndian.bytes())
block.replaceSubrange(32..<36, with: x8.bigEndian.bytes())
block.replaceSubrange(36..<40, with: x9.bigEndian.bytes())
block.replaceSubrange(40..<44, with: x10.bigEndian.bytes())
block.replaceSubrange(44..<48, with: x11.bigEndian.bytes())
block.replaceSubrange(48..<52, with: x12.bigEndian.bytes())
block.replaceSubrange(52..<56, with: x13.bigEndian.bytes())
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: Salsa20.blockSize)
var bytesSlice = bytes
var out = Array<UInt8>(reserveCapacity: bytesSlice.count)

while bytesSlice.count >= Salsa20.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 + Salsa20.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 Salsa20: 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 Salsa20 {
public struct SalsaEncryptor: Cryptor, Updatable {
private var accumulated = Array<UInt8>()
private let salsa: Salsa20

init(salsa: Salsa20) {
self.salsa = salsa
}

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: Salsa20.blockSize) {
if isLast || accumulated.count >= Salsa20.blockSize {
encrypted += try salsa.encrypt(chunk)
accumulated.removeFirst(chunk.count) // TODO: improve performance
}
}
return encrypted
}

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

// MARK: Decryptor

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

private var offset: Int = 0
private var offsetToRemove: Int = 0
private let salsa: Salsa20

init(salsa: Salsa20) {
self.salsa = salsa
}

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: Salsa20.blockSize) {
if isLast || accumulated.count >= Salsa20.blockSize {
plaintext += try salsa.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 Salsa20: Cryptors {
//TODO: Use BlockEncryptor/BlockDecryptor

public func makeEncryptor() -> Cryptor & Updatable {
return Salsa20.SalsaEncryptor(salsa: self)
}

public func makeDecryptor() -> Cryptor & Updatable {
return Salsa20.SalsaDecryptor(salsa: self)
}
}
Loading

0 comments on commit dc03424

Please sign in to comment.