Skip to content

Instantly share code, notes, and snippets.

@marcprux
Created May 1, 2018 16:13
A single-file SHA1 hash function, modified from the CryptoSwift project
//
// SHA1.swift
// Glib
//
// Created by Marc Prud'hommeaux on 5/1/18.
// Copyright © 2018 Marc Prud'hommeaux. All rights reserved.
//
// A single-file conversion of the SHA1 code from: CryptoSwift
//
// Copyright (C) 2014-2017 Marcin Krzyżanowski <marcin@krzyzanowskim.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.
//
// 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 final class SHA1 {
static let digestLength: Int = 20 // 160 / 8
static let blockSize: Int = 64
fileprivate static let hashInitialValue: ContiguousArray<UInt32> = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]
fileprivate var accumulated = Array<UInt8>()
fileprivate var processedBytesTotalCount: Int = 0
fileprivate var accumulatedHash: ContiguousArray<UInt32> = SHA1.hashInitialValue
public init() {
}
/// Calculates the SHA1 hash for a given array of bytes
public func calculate(for bytes: Array<UInt8>) throws -> Array<UInt8> {
return try update(withBytes: bytes[bytes.startIndex..<bytes.endIndex], isLast: true)
}
/// Updates the SHA1 hash with a given array of bytes, returning the current hash
public func update(withBytes bytes: ArraySlice<UInt8>, isLast: Bool = false) throws -> Array<UInt8> {
accumulated += bytes
func arrayOfBytes(value: Int, length totalBytes: Int = MemoryLayout<Int>.size) -> Array<UInt8> {
let valuePointer = UnsafeMutablePointer<Int>.allocate(capacity: 1)
valuePointer.pointee = value
let bytesPointer = UnsafeMutablePointer<UInt8>(OpaquePointer(valuePointer))
var bytes = Array<UInt8>(repeating: 0, count: totalBytes)
for j in 0..<min(MemoryLayout<Int>.size, totalBytes) {
bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
}
valuePointer.deinitialize(count: 1)
valuePointer.deallocate()
return bytes
}
if isLast {
let lengthInBits = (processedBytesTotalCount + accumulated.count) * 8
let lengthBytes = arrayOfBytes(value: lengthInBits.littleEndian, length: 64 / 8) // A 64-bit representation of b
// Step 1. Append padding
bitPadding(to: &accumulated, blockSize: SHA1.blockSize, allowance: 64 / 8)
// Step 2. Append Length a 64-bit representation of lengthInBits
accumulated += lengthBytes
}
var processedBytes = 0
for chunk in BatchedCollection(base: accumulated, size: SHA1.blockSize) {
if isLast || (accumulated.count - processedBytes) >= SHA1.blockSize {
process(block: chunk, currentHash: &accumulatedHash)
processedBytes += chunk.count
}
}
accumulated.removeFirst(processedBytes)
processedBytesTotalCount += processedBytes
// output current hash
var result = Array<UInt8>(repeating: 0, count: SHA1.digestLength)
var pos = 0
for idx in 0..<accumulatedHash.count {
let h = accumulatedHash[idx]
result[pos + 0] = UInt8((h >> 24) & 0xff)
result[pos + 1] = UInt8((h >> 16) & 0xff)
result[pos + 2] = UInt8((h >> 8) & 0xff)
result[pos + 3] = UInt8(h & 0xff)
pos += 4
}
// reset hash value for instance
if isLast {
accumulatedHash = SHA1.hashInitialValue
}
return result
}
private func process(block chunk: ArraySlice<UInt8>, currentHash hh: inout ContiguousArray<UInt32>) {
func rotateLeft(_ value: UInt32, by: UInt32) -> UInt32 {
return ((value << by) & 0xffffffff) | (value >> (32 - by))
}
// break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15, big-endian
// Extend the sixteen 32-bit words into eighty 32-bit words:
let M = UnsafeMutablePointer<UInt32>.allocate(capacity: 80)
M.initialize(repeating: 0, count: 80)
defer {
M.deinitialize(count: 80)
M.deallocate()
}
for x in 0..<80 {
switch x {
case 0...15:
let start = chunk.startIndex.advanced(by: x * 4) // * MemoryLayout<UInt32>.size
M[x] = createUInt32(bytes: chunk, fromIndex: start)
break
default:
M[x] = rotateLeft(M[x - 3] ^ M[x - 8] ^ M[x - 14] ^ M[x - 16], by: 1)
break
}
}
var A = hh[0]
var B = hh[1]
var C = hh[2]
var D = hh[3]
var E = hh[4]
// Main loop
for j in 0...79 {
var f: UInt32 = 0
var k: UInt32 = 0
switch j {
case 0...19:
f = (B & C) | ((~B) & D)
k = 0x5a827999
break
case 20...39:
f = B ^ C ^ D
k = 0x6ed9eba1
break
case 40...59:
f = (B & C) | (B & D) | (C & D)
k = 0x8f1bbcdc
break
case 60...79:
f = B ^ C ^ D
k = 0xca62c1d6
break
default:
break
}
let temp = rotateLeft(A, by: 5) &+ f &+ E &+ M[j] &+ k
E = D
D = C
C = rotateLeft(B, by: 30)
B = A
A = temp
}
hh[0] = hh[0] &+ A
hh[1] = hh[1] &+ B
hh[2] = hh[2] &+ C
hh[3] = hh[3] &+ D
hh[4] = hh[4] &+ E
}
/** array of bytes */
func createUInt32(bytes: ArraySlice<UInt8>, fromIndex index: ArraySlice<UInt8>.Index) -> UInt32 {
if bytes.isEmpty {
return 0
}
let count = bytes.count
let val0 = count > 0 ? UInt32(bytes[index.advanced(by: 0)]) << 24 : 0
let val1 = count > 1 ? UInt32(bytes[index.advanced(by: 1)]) << 16 : 0
let val2 = count > 2 ? UInt32(bytes[index.advanced(by: 2)]) << 8 : 0
let val3 = count > 3 ? UInt32(bytes[index.advanced(by: 3)]) : 0
return val0 | val1 | val2 | val3
}
/**
ISO/IEC 9797-1 Padding method 2.
Add a single bit with value 1 to the end of the data.
If necessary add bits with value 0 to the end of the data until the padded data is a multiple of blockSize.
- parameters:
- blockSize: Padding size in bytes.
- allowance: Excluded trailing number of bytes.
*/
@inline(__always)
func bitPadding(to data: inout Array<UInt8>, blockSize: Int, allowance: Int = 0) {
let msgLength = data.count
// Step 1. Append Padding Bits
// append one bit (UInt8 with one bit) to message
data.append(0x80)
// Step 2. append "0" bit until message length in bits ≡ 448 (mod 512)
let max = blockSize - allowance // 448, 986
if msgLength % blockSize < max { // 448
data += Array<UInt8>(repeating: 0, count: max - 1 - (msgLength % blockSize))
} else {
data += Array<UInt8>(repeating: 0, count: blockSize + max - 1 - (msgLength % blockSize))
}
}
private struct BatchedCollection<Base: Collection>: Collection {
let base: Base
let size: Int
typealias Index = BatchedCollectionIndex<Base>
private func nextBreak(after idx: Base.Index) -> Base.Index {
return base.index(idx, offsetBy: size, limitedBy: base.endIndex) ?? base.endIndex
}
var startIndex: Index {
return Index(range: base.startIndex..<nextBreak(after: base.startIndex))
}
var endIndex: Index {
return Index(range: base.endIndex..<base.endIndex)
}
func index(after idx: Index) -> Index {
return Index(range: idx.range.upperBound..<nextBreak(after: idx.range.upperBound))
}
subscript(idx: Index) -> Base.SubSequence {
return base[idx.range]
}
}
private struct BatchedCollectionIndex<Base: Collection>: Comparable {
let range: Range<Base.Index>
static func == <Base>(lhs: BatchedCollectionIndex<Base>, rhs: BatchedCollectionIndex<Base>) -> Bool {
return lhs.range.lowerBound == rhs.range.lowerBound
}
static func < <Base>(lhs: BatchedCollectionIndex<Base>, rhs: BatchedCollectionIndex<Base>) -> Bool {
return lhs.range.lowerBound < rhs.range.lowerBound
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment