1 | package fdh |
||
2 | |||
3 | import ( |
||
4 | "crypto" |
||
5 | "hash" |
||
6 | "math" |
||
7 | "strconv" |
||
8 | "sync" |
||
9 | ) |
||
10 | |||
11 | // digest represents the partial evaluation of a Full Domain Hash checksum. |
||
12 | type digest struct { |
||
13 | base crypto.Hash |
||
14 | bits int |
||
15 | parts []hash.Hash |
||
16 | final bool // Unlike many hash functions Full Domain Hashes are "finalized" after a call to Sum() and cannot be furthur written to. |
||
17 | } |
||
18 | |||
19 | // New returns a hash.Hash for computing a Full Domain Hash checksum, given a base hash function and a target bit length. |
||
20 | // It will panic if the bitlen is not a multiple of the hash length or if the hash library is not imported. |
||
21 | func New(h crypto.Hash, bitlen int) hash.Hash { |
||
22 | if !h.Available() { |
||
23 | panic("fdh: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable. Make sure your hash function is proprely imported.") |
||
24 | } else if bitlen%8 != 0 { |
||
25 | panic("fdh: hash digest size should be a multiple of 8") |
||
26 | } else if bitlen <= 0 { |
||
27 | panic("fdh: hash digest size cannot be less or equal to zero") |
||
28 | } |
||
29 | |||
30 | numparts := int(math.Ceil(float64(bitlen) / float64((h.Size() * 8)))) |
||
31 | |||
32 | d := digest{ |
||
33 | base: h, |
||
34 | bits: bitlen, |
||
35 | parts: make([]hash.Hash, numparts, numparts), |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
36 | } |
||
37 | d.Reset() |
||
38 | return &d |
||
39 | } |
||
40 | |||
41 | // Reset resets the Hash to its initial state. |
||
42 | func (d *digest) Reset() { |
||
43 | for i := range d.parts { |
||
44 | d.parts[i] = d.base.New() |
||
45 | } |
||
46 | d.final = false |
||
47 | } |
||
48 | |||
49 | // BlockSize returns the hash's underlying block size. |
||
50 | func (d *digest) BlockSize() int { |
||
51 | return d.parts[0].BlockSize() |
||
52 | } |
||
53 | |||
54 | // Size returns the number of bytes Sum will return. This will be the same as the bitlen (with conversion from bits to bytes) |
||
55 | func (d *digest) Size() int { |
||
56 | return d.bits / 8 |
||
57 | } |
||
58 | |||
59 | // Add more data to the running hash. |
||
60 | // Once Sum() is called the hash is finalized and writing to the hash will panic |
||
61 | func (d *digest) Write(p []byte) (int, error) { |
||
62 | if d.final { |
||
63 | panic("Cannot write to Full Domain Hash after a call has been made to Sum() and the hash has been finalized.") |
||
64 | } |
||
65 | |||
66 | // Write to each component hash asyncronously |
||
67 | var wg sync.WaitGroup |
||
68 | for i, h := range d.parts { |
||
69 | wg.Add(1) |
||
70 | go func(i int, h hash.Hash) { |
||
71 | h.Write(p) // Hashes in crypto library don't return errors |
||
0 ignored issues
–
show
|
|||
72 | wg.Done() |
||
73 | }(i, h) |
||
74 | } |
||
75 | wg.Wait() |
||
76 | |||
77 | return len(p), nil |
||
78 | } |
||
79 | |||
80 | // Sum appends the current hash to b and returns the resulting slice. |
||
81 | // Once Sum is called, the hash is finalized and can no longer be written to |
||
82 | func (d *digest) Sum(in []byte) []byte { |
||
83 | hash := d.checkSum() |
||
84 | return append(in, hash[:]...) |
||
85 | } |
||
86 | |||
87 | func (d *digest) checkSum() []byte { |
||
88 | var sum []byte |
||
89 | for i, h := range d.parts { |
||
90 | if !d.final { |
||
91 | finalByte := byte(i) |
||
92 | h.Write([]byte{finalByte}) |
||
0 ignored issues
–
show
|
|||
93 | } |
||
94 | sum = append(sum, h.Sum(nil)...) |
||
95 | } |
||
96 | d.final = true |
||
97 | return sum[:d.bits/8] |
||
98 | } |
||
99 | |||
100 | // Sum returns the the Full Domain Hash checksum of the data. |
||
101 | func Sum(h crypto.Hash, bitlen int, message []byte) []byte { |
||
102 | hash := New(h, bitlen) |
||
103 | hash.Write(message) |
||
0 ignored issues
–
show
|
|||
104 | return hash.Sum(nil) |
||
105 | } |
||
106 |