-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathSHA1.swift
182 lines (160 loc) · 7.74 KB
/
SHA1.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SHA-1 implementation in Swift 4
// $AUTHOR: Iggy Drougge
// $VER: 2.3.1
import Foundation
/// Left rotation (or cyclic shift) operator
infix operator <<< : BitwiseShiftPrecedence
private func <<< (lhs:UInt32, rhs:UInt32) -> UInt32 {
return lhs << rhs | lhs >> (32-rhs)
}
public struct SHA1 {
// One chunk consists of 80 big-endian longwords (32 bits, unsigned)
private static let CHUNKSIZE=80
// SHA-1 magic words
private static let h0:UInt32 = 0x67452301
private static let h1:UInt32 = 0xEFCDAB89
private static let h2:UInt32 = 0x98BADCFE
private static let h3:UInt32 = 0x10325476
private static let h4:UInt32 = 0xC3D2E1F0
/**************************************************
* SHA1.context *
* The context struct contains volatile variables *
* as well as the actual hashing function. *
**************************************************/
private struct context {
// Initialise variables:
var h:[UInt32]=[SHA1.h0,SHA1.h1,SHA1.h2,SHA1.h3,SHA1.h4]
// Process one chunk of 80 big-endian longwords
mutating func process(chunk:inout ContiguousArray<UInt32>) {
for i in 0..<16 {
chunk[i] = chunk[i].bigEndian // The numbers must be big-endian
}
//chunk=chunk.map{$0.bigEndian} // The numbers must be big-endian
for i in 16...79 { // Extend the chunk to 80 longwords
chunk[i] = (chunk[i-3] ^ chunk[i-8] ^ chunk[i-14] ^ chunk[i-16]) <<< 1
}
// Initialise hash value for this chunk:
var a,b,c,d,e,f,k,temp:UInt32
a=h[0]; b=h[1]; c=h[2]; d=h[3]; e=h[4]
f=0x0; k=0x0
// Main loop
for i in 0...79 {
switch i {
case 0...19:
f = (b & c) | ((~b) & d)
k = 0x5A827999
case 20...39:
f = b ^ c ^ d
k = 0x6ED9EBA1
case 40...59:
f = (b & c) | (b & d) | (c & d)
k = 0x8F1BBCDC
case 60...79:
f = b ^ c ^ d
k = 0xCA62C1D6
default: break
}
temp = a <<< 5 &+ f &+ e &+ k &+ chunk[i]
e = d
d = c
c = b <<< 30
b = a
a = temp
//print(String(format: "t=%d %08X %08X %08X %08X %08X", i, a, b, c, d, e))
}
// Add this chunk's hash to result so far:
h[0] = h[0] &+ a
h[1] = h[1] &+ b
h[2] = h[2] &+ c
h[3] = h[3] &+ d
h[4] = h[4] &+ e
}
}
/**************************************************
* processData() *
* All inputs are processed as NSData. *
* This function splits the data into chunks of *
* 16 longwords (64 bytes, 512 bits), *
* padding the chunk as necessary. *
**************************************************/
private static func process(data: inout Data) -> SHA1.context? {
var context=SHA1.context()
var w = ContiguousArray<UInt32>(repeating: 0x00000000, count: CHUNKSIZE) // Initialise empty chunk
let ml=data.count << 3 // Message length in bits
var range = 0..<64 // A chunk is 64 bytes
// If the remainder of the message is more than or equal 64 bytes
while data.count >= range.upperBound {
//print("Reading \(range.count) bytes @ position \(range.lowerBound)")
w.withUnsafeMutableBufferPointer{ dest in
_=data.copyBytes(to: dest, from: range) // Retrieve one chunk
}
context.process(chunk: &w) // Process the chunk
range = range.upperBound..<range.upperBound+64 // Make range for next chunk
}
// Handle remainder of message that is <64 bytes in length
w = ContiguousArray<UInt32>(repeating: 0x00000000, count: CHUNKSIZE) // Initialise empty chunk
range = range.lowerBound..<data.count // Range for remainder of message
w.withUnsafeMutableBufferPointer{ dest in
_=data.copyBytes(to: dest, from: range) // Retrieve remainder
}
let bytetochange=range.count % 4 // The bit to the right of the
let shift = UInt32(bytetochange * 8) // last bit of the actual message
w[range.count/4] |= 0x80 << shift // should be set to 1.
// If the remainder overflows, a new, empty chunk must be added
if range.count+1 > 56 {
context.process(chunk: &w)
w = ContiguousArray<UInt32>(repeating: 0x00000000, count: CHUNKSIZE)
}
// The last 64 bits of the last chunk must contain the message length in big-endian format
w[15] = UInt32(ml).bigEndian
context.process(chunk: &w) // Process the last chunk
// The context (or nil) is returned, containing the hash in the h[] array
return context
}
/**************************************************
* hexString() *
* Render the hash as a hexadecimal string *
**************************************************/
private static func hexString(_ context:SHA1.context?) -> String? {
guard let c=context else {return nil}
return String(format: "%08X %08X %08X %08X %08X", c.h[0], c.h[1], c.h[2], c.h[3], c.h[4])
}
/**************************************************
* dataFromFile() *
* Fetch the contents of a file as NSData *
* for processing by processData() *
**************************************************/
private static func dataFromFile(named filename:String) -> SHA1.context? {
guard var file = try? Data(contentsOf: URL(fileURLWithPath: filename)) else {return nil}
return process(data: &file)
}
/**************************************************
* PUBLIC METHODS *
**************************************************/
/// Return a hexadecimal hash from a file
static public func hexString(fromFile filename:String) -> String? {
return hexString(SHA1.dataFromFile(named: filename))
}
/// Return the hash of a file as an array of Ints
public static func hash(fromFile filename:String) -> [Int]? {
return dataFromFile(named: filename)?.h.map{Int($0)}
}
/// Return a hexadecimal hash from NSData
public static func hexString(from data: inout Data) -> String? {
return hexString(SHA1.process(data: &data))
}
/// Return the hash of NSData as an array of Ints
public static func hash(from data: inout Data) -> [Int]? {
return process(data: &data)?.h.map{Int($0)}
}
/// Return a hexadecimal hash from a string
public static func hexString(from str:String) -> String? {
guard var data = str.data(using: .utf8) else { return nil }
return hexString(SHA1.process(data: &data))
}
/// Return the hash of a string as an array of Ints
public static func hash(from str:String) -> [Int]? {
guard var data = str.data(using: .utf8) else { return nil }
return process(data: &data)?.h.map{Int($0)}
}
}