Skip to content

Commit e3a9f84

Browse files
committed
Merge pull request #13 from staticfloat/sf/sha3
Fairly big cleanup, SHA3 and bytes instead of strings!
2 parents 90144b2 + 54b1fc1 commit e3a9f84

10 files changed

+373
-136
lines changed

README.md

+24-4
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,41 @@ Usage is very straightforward:
1010
```
1111
julia> using SHA
1212
13-
julia> sha256("test")
13+
julia> bytes2hex(sha256("test"))
1414
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
1515
```
1616

17-
Each exported function (at the time of this writing, only SHA-1, SHA-2 224, 256, 384 and 512 functions are implemented) takes in either an `Array{UInt8}`, a `ByteString` or an `IO` object. This makes it trivial to checksum a file:
17+
Each exported function (at the time of this writing, SHA-1, SHA-2 224, 256, 384 and 512, and SHA-3 224, 256, 384 and 512 functions are implemented) takes in either an `Array{UInt8}`, a `ByteString` or an `IO` object. This makes it trivial to checksum a file:
1818

1919
```
2020
shell> cat /tmp/test.txt
2121
test
2222
julia> using SHA
2323
2424
julia> open("/tmp/test.txt") do f
25-
sha256(f)
25+
sha2_256(f)
2626
end
27-
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
27+
32-element Array{UInt8,1}:
28+
0x9f
29+
0x86
30+
0xd0
31+
0x81
32+
0x88
33+
0x4c
34+
0x7d
35+
0x65
36+
37+
0x5d
38+
0x6c
39+
0x15
40+
0xb0
41+
0xf0
42+
0x0a
43+
0x08
2844
```
2945

3046
Note the lack of a newline at the end of `/tmp/text.txt`. Julia automatically inserts a newline before the `julia>` prompt.
47+
48+
Due to the colloquial usage of `sha256` to refer to `sha2_256`, convenience functions are provided, mapping `shaxxx()` function calls to `sha2_xxx()`. For SHA-3, no such colloquialisms exist and the user must use the full `sha3_xxx()` names.
49+
50+
Note that, at the time of this writing, the SHA3 code is not optimized, and as such is roughly an order of magnitude slower than SHA2. Pull requests are welcome.

src/SHA.jl

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,57 @@
1-
isdefined(Base, :__precompile__) && __precompile__()
1+
#isdefined(Base, :__precompile__) && __precompile__()
22

33
module SHA
44

55
using Compat
66

7-
export sha1, sha224, sha256, sha384, sha512
7+
# Export convenience functions, context types, update!() and digest!() functions
8+
export sha1, SHA1_CTX, update!, digest!
9+
export sha224, sha256, sha384, sha512
10+
export sha2_224, sha2_256, sha2_384, sha2_512
11+
export sha3_224, sha3_256, sha3_384, sha3_512
12+
export SHA224_CTX, SHA256_CTX, SHA384_CTX, SHA512_CTX
13+
export SHA2_224_CTX, SHA2_256_CTX, SHA2_384_CTX, SHA2_512_CTX
14+
export SHA3_224_CTX, SHA3_256_CTX, SHA3_384_CTX, SHA3_512_CTX
15+
816

917
include("constants.jl")
1018
include("types.jl")
1119
include("base_functions.jl")
1220
include("sha1.jl")
1321
include("sha2.jl")
22+
include("sha3.jl")
1423
include("common.jl")
1524

16-
1725
# Create data types and convenience functions for each hash implemented
1826
for (f, ctx) in [(:sha1, :SHA1_CTX),
1927
(:sha224, :SHA224_CTX),
2028
(:sha256, :SHA256_CTX),
2129
(:sha384, :SHA384_CTX),
22-
(:sha512, :SHA512_CTX)]
30+
(:sha512, :SHA512_CTX),
31+
(:sha2_224, :SHA2_224_CTX),
32+
(:sha2_256, :SHA2_256_CTX),
33+
(:sha2_384, :SHA2_384_CTX),
34+
(:sha2_512, :SHA2_512_CTX),
35+
(:sha3_224, :SHA3_224_CTX),
36+
(:sha3_256, :SHA3_256_CTX),
37+
(:sha3_384, :SHA3_384_CTX),
38+
(:sha3_512, :SHA3_512_CTX),]
2339
@eval begin
24-
# Allows things like:
25-
# open("test.txt") do f
26-
# sha256(f)
27-
# done
28-
function $f(io::IO)
40+
# Our basic function is to process arrays of bytes
41+
function $f(data::Array{UInt8,1})
2942
ctx = $ctx()
30-
update!(ctx, readbytes(io));
31-
return bytes2hex(digest!(ctx))
43+
update!(ctx, data);
44+
return digest!(ctx)
3245
end
3346

34-
# Allows the same as above, but on ByteStrings and Arrays
35-
$f(str::ByteString) = $f(IOBuffer(str))
36-
$f(arr::Array{UInt8,1}) = $f(IOBuffer(arr))
47+
# ByteStrings are a pretty handy thing to be able to crunch through
48+
$f(str::ByteString) = $f(str.data)
49+
50+
# Convenience function for IO devices, allows for things like:
51+
# open("test.txt") do f
52+
# sha256(f)
53+
# done
54+
$f(io::IO) = $f(readbytes(io))
3755
end
3856
end
3957

src/base_functions.jl

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ R(b,x) = (x >> b)
1717
S32(b,x) = rrot(b,x,32)
1818
# 64-bit Rotate-right (used in SHA-384 and SHA-512):
1919
S64(b,x) = rrot(b,x,64)
20+
# 64-bit Rotate-left (used in SHA3)
21+
L64(b,x) = lrot(b,x,64)
2022

2123
# Two of six logical functions used in SHA-256, SHA-384, and SHA-512:
2224
Ch(x,y,z) = ((x & y) $ (~x & z))

src/common.jl

+19-46
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,39 @@
22

33
# update! takes in variable-length data, buffering it into blocklen()-sized pieces,
44
# calling transform!() when necessary to update the internal hash state.
5-
function update!{T<:SHA_CTX}(context::T, data::Array{UInt8,1})
6-
if length(data) == 0
7-
return
8-
end
5+
function update!{T<:Union{SHA1_CTX,SHA2_CTX,SHA3_CTX}}(context::T, data::Array{UInt8,1})
6+
# We need to do all our arithmetic in the proper bitwidth
7+
UIntXXX = typeof(context.bytecount)
98

10-
data_idx = 0
11-
len = convert(typeof(context.bytecount), length(data))
9+
# Process as many complete blocks as possible
10+
len = UIntXXX(length(data))
11+
data_idx = UIntXXX(0)
1212
usedspace = context.bytecount % blocklen(T)
13-
if usedspace > 0
14-
# Calculate how much free space is available in the buffer
15-
freespace = blocklen(T) - usedspace
16-
17-
if len >= freespace
18-
# Fill the buffer completely and process it
19-
for i in 1:freespace
20-
context.buffer[usedspace + i] = data[data_idx + i]
21-
end
22-
23-
# Round bytecount up to the nearest blocklen
24-
context.bytecount += freespace
25-
data_idx += freespace
26-
len -= freespace
27-
transform!(context)
28-
else
29-
# The buffer is not yet full
30-
for i = 1:len
31-
context.buffer[usedspace + i] = data[data_idx + i]
32-
end
33-
context.bytecount += len
34-
return
13+
while len - data_idx + usedspace >= blocklen(T)
14+
# Fill up as much of the buffer as we can with the data given us
15+
for i in 1:(blocklen(T) - usedspace)
16+
context.buffer[usedspace + i] = data[data_idx + i]
3517
end
36-
end
3718

38-
39-
# Process as many complete blocks as possible, now that the buffer is full
40-
data_idx = one(len)
41-
while len - (data_idx - 1) >= blocklen(T)
42-
for i in 1:blocklen(T)
43-
context.buffer[i] = data[data_idx + i - 1]
44-
end
4519
transform!(context)
46-
data_idx += blocklen(T)
20+
context.bytecount += blocklen(T) - usedspace
21+
data_idx += blocklen(T) - usedspace
22+
usedspace = UIntXXX(0)
4723
end
48-
context.bytecount += (data_idx - 1)
4924

50-
# If there are leftovers, save them in buffer until next update!() or digest!()
51-
if data_idx < len
52-
# There's left-overs, so save 'em
53-
for i = 1:(len - data_idx + 1)
54-
context.buffer[i] = data[data_idx + i - 1]
25+
# There is less than a complete block left, but we need to save the leftovers into context.buffer:
26+
if len > data_idx
27+
for i = 1:(len - data_idx)
28+
context.buffer[usedspace + i] = data[data_idx + i]
5529
end
56-
context.bytecount += (len - data_idx + 1)
30+
context.bytecount += len - data_idx
5731
end
5832
end
5933

6034

6135
# Clear out any saved data in the buffer, append total bitlength, and return our precious hash!
62-
function digest!{T<:SHA_CTX}(context::T)
36+
function digest!{T<:Union{SHA1_CTX,SHA2_CTX}}(context::T)
6337
usedspace = context.bytecount % blocklen(T)
64-
6538
# If we have anything in the buffer still, pad and transform that data
6639
if usedspace > 0
6740
# Begin padding with a 1 bit:

src/constants.jl

+28-4
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ const K256 = UInt32[
3333
]
3434

3535
# Initial hash value H for SHA-224:
36-
const SHA224_initial_hash_value = UInt32[
36+
const SHA2_224_initial_hash_value = UInt32[
3737
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
3838
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
3939
]
4040

4141

42-
const SHA256_initial_hash_value = UInt32[
42+
const SHA2_256_initial_hash_value = UInt32[
4343
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
4444
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
4545
]
@@ -89,17 +89,41 @@ const K512 = UInt64[
8989
]
9090

9191
# Initial hash value H for SHA-384
92-
const SHA384_initial_hash_value = UInt64[
92+
const SHA2_384_initial_hash_value = UInt64[
9393
0xcbbb9d5dc1059ed8, 0x629a292a367cd507,
9494
0x9159015a3070dd17, 0x152fecd8f70e5939,
9595
0x67332667ffc00b31, 0x8eb44a8768581511,
9696
0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4
9797
]
9898

9999
# Initial hash value H for SHA-512
100-
const SHA512_initial_hash_value = UInt64[
100+
const SHA2_512_initial_hash_value = UInt64[
101101
0x6a09e667f3bcc908, 0xbb67ae8584caa73b,
102102
0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
103103
0x510e527fade682d1, 0x9b05688c2b3e6c1f,
104104
0x1f83d9abfb41bd6b, 0x5be0cd19137e2179
105105
]
106+
107+
# Round constants for SHA3 rounds
108+
const SHA3_ROUND_CONSTS = UInt64[
109+
0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
110+
0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
111+
0x8000000080008081, 0x8000000000008009, 0x000000000000008a,
112+
0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
113+
0x000000008000808b, 0x800000000000008b, 0x8000000000008089,
114+
0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
115+
0x000000000000800a, 0x800000008000000a, 0x8000000080008081,
116+
0x8000000000008080, 0x0000000080000001, 0x8000000080008008
117+
]
118+
119+
# Rotation constants for SHA3 rounds
120+
const SHA3_ROTC = UInt64[
121+
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
122+
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44
123+
]
124+
125+
# Permutation indices for SHA3 rounds (+1'ed so as to work with julia's 1-based indexing)
126+
const SHA3_PILN = UInt64[
127+
11, 8, 12, 18, 19, 4, 6, 17, 9, 22, 25, 5,
128+
16, 24, 20, 14, 13, 3, 21, 15, 23, 10, 7, 2
129+
]

src/sha2.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function transform!{T<:SHA2_CTX_SMALL}(context::T)
1+
function transform!{T<:Union{SHA2_224_CTX,SHA2_256_CTX}}(context::T)
22
buffer = reinterpret(eltype(context.state), context.buffer)
33
# Initialize registers with the previous intermediate values (our state)
44
a = context.state[1]
@@ -65,7 +65,7 @@ function transform!{T<:SHA2_CTX_SMALL}(context::T)
6565
end
6666

6767

68-
function transform!(context::SHA2_CTX_BIG)
68+
function transform!(context::Union{SHA2_384_CTX,SHA2_512_CTX})
6969
buffer = reinterpret(eltype(context.state), context.buffer)
7070
# Initialize registers with the prev. intermediate value
7171
a = context.state[1]

src/sha3.jl

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
function transform!{T<:SHA3_CTX}(context::T)
2+
# First, update state with buffer
3+
buffer_as_uint64 = reinterpret(eltype(context.state), context.buffer)
4+
for idx in 1:div(blocklen(T),8)
5+
context.state[idx] $= buffer_as_uint64[idx]
6+
end
7+
bc = Array{UInt64,1}(5)
8+
9+
# We always assume 24 rounds
10+
for round in 0:23
11+
# Theta function
12+
for i in 1:5
13+
bc[i] = context.state[i] $ context.state[i + 5] $ context.state[i + 10] $ context.state[i + 15] $ context.state[i + 20]
14+
end
15+
16+
for i in 1:5
17+
temp = bc[mod1(i + 4, 5)] $ L64(1, bc[mod1(i + 1, 5)])
18+
for j in 0:5:20
19+
context.state[i + j] $= temp
20+
end
21+
end
22+
23+
# Rho Pi
24+
temp = context.state[2]
25+
for i in 1:24
26+
j = SHA3_PILN[i]
27+
bc[1] = context.state[j]
28+
context.state[j] = L64(SHA3_ROTC[i], temp)
29+
temp = bc[1]
30+
end
31+
32+
# Chi
33+
for j in 0:5:20
34+
for i in 1:5
35+
bc[i] = context.state[i + j]
36+
end
37+
for i in 1:5
38+
context.state[j + i] $= (~bc[mod1(i + 1, 5)] & bc[mod1(i + 2, 5)])
39+
end
40+
end
41+
42+
# Iota
43+
context.state[1] $= SHA3_ROUND_CONSTS[round+1]
44+
end
45+
46+
return context.state
47+
end
48+
49+
50+
51+
# Finalize data in the buffer, append total bitlength, and return our precious hash!
52+
function digest!{T<:SHA3_CTX}(context::T)
53+
usedspace = context.bytecount % blocklen(T)
54+
# If we have anything in the buffer still, pad and transform that data
55+
if usedspace < blocklen(T) - 1
56+
# Begin padding with a 0x06
57+
context.buffer[usedspace+1] = 0x06
58+
# Fill with zeros up until the last byte
59+
context.buffer[usedspace+2:end-1] = 0x00
60+
# Finish it off with a 0x80
61+
context.buffer[end] = 0x80
62+
else
63+
# Otherwise, we have to add on a whole new buffer just for the zeros and 0x80
64+
context.buffer[end] = 0x06
65+
transform!(context)
66+
67+
context.buffer[1:end-1] = 0x0
68+
context.buffer[end] = 0x80
69+
end
70+
71+
# Final transform:
72+
transform!(context)
73+
74+
# Return the digest
75+
return reinterpret(UInt8, context.state)[1:digestlen(T)]
76+
end

0 commit comments

Comments
 (0)