]> Cypherpunks.ru repositories - balloon.git/blob - balloon.go
Namespace is changed to go.cypherpunks.ru/balloon
[balloon.git] / balloon.go
1 /*
2 balloon -- Balloon password hashing function
3 Copyright (C) 2016-2019 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this program.  If not, see
16 <http://www.gnu.org/licenses/>.
17 */
18
19 // Balloon password hashing.
20 //
21 // Look https://crypto.stanford.edu/balloon/ for more description.
22 package balloon
23
24 import (
25         "encoding/binary"
26         "hash"
27         "math/big"
28 )
29
30 const (
31         delta = 3
32 )
33
34 // This function takes hash, password, salt, space cost (buffer size,
35 // number of hash-output sized blocks), time cost (number of rounds) and
36 // performs the following:
37 //
38 //    # Expand input into buffer.
39 //    buf[0] = hash(cnt++ || passwd || salt)
40 //    for m from 1 to sCost-1:
41 //        buf[m] = hash(cnt++ || buf[m-1])
42 //    # Mix buffer contents.
43 //    for t from 0 to tCost-1:
44 //        for m from 0 to sCost-1:
45 //            # Hash last and current blocks.
46 //            prev = buf[(m-1) mod sCost]
47 //            buf[m] = hash(cnt++ || prev || buf[m])
48 //            # Hash in pseudorandomly chosen blocks.
49 //            for i from 0 to delta-1:
50 //                other = to_int(hash(cnt++ || salt || t || m || i)) mod sCost
51 //                buf[m] = hash(cnt++ || buf[m] || buf[other])
52 //    # Extract output from buffer.
53 //    return buf[sCost-1]
54 func B(h hash.Hash, passwd, salt []byte, sCost, tCost uint64) []byte {
55         var cnt uint64
56         intBuf := make([]byte, 8)
57         hSize := uint64(h.Size())
58         buf := make([]byte, 0, sCost*hSize)
59         // Expand input into buffer
60         binary.BigEndian.PutUint64(intBuf, cnt)
61         cnt++
62         h.Write(intBuf)
63         h.Write(passwd)
64         h.Write(salt)
65         buf = h.Sum(buf)
66         var m uint64
67         for m = 1; m < sCost; m++ {
68                 binary.BigEndian.PutUint64(intBuf, cnt)
69                 cnt++
70                 h.Reset()
71                 h.Write(intBuf)
72                 h.Write(buf[(m-1)*hSize : m*hSize])
73                 buf = h.Sum(buf)
74         }
75         // Mix buffer contents
76         var prev []byte
77         var i uint64
78         var neigh uint64
79         bi := big.NewInt(0)
80         bs := big.NewInt(int64(sCost))
81         biBuf := make([]byte, 0, h.Size())
82         for t := uint64(0); t < tCost; t++ {
83                 for m = 0; m < sCost; m++ {
84                         // Hash last and current blocks
85                         if m == 0 {
86                                 prev = buf[(sCost-1)*hSize:]
87                         } else {
88                                 prev = buf[(m-1)*hSize : m*hSize]
89                         }
90                         binary.BigEndian.PutUint64(intBuf, cnt)
91                         cnt++
92                         h.Reset()
93                         h.Write(intBuf)
94                         h.Write(prev)
95                         h.Write(buf[m*hSize : (m+1)*hSize])
96                         buf = h.Sum(buf[:m*hSize])
97
98                         // Hash in pseudorandomly chosen blocks
99                         for i = 0; i < delta; i++ {
100                                 binary.BigEndian.PutUint64(intBuf, cnt)
101                                 cnt++
102                                 h.Reset()
103                                 h.Write(intBuf)
104                                 h.Write(salt)
105                                 binary.BigEndian.PutUint64(intBuf, t)
106                                 h.Write(intBuf)
107                                 binary.BigEndian.PutUint64(intBuf, m)
108                                 h.Write(intBuf)
109                                 binary.BigEndian.PutUint64(intBuf, i)
110                                 h.Write(intBuf)
111                                 biBuf = h.Sum(biBuf[:0])
112                                 bi.SetBytes(biBuf)
113                                 bi.Mod(bi, bs)
114                                 binary.BigEndian.PutUint64(intBuf, cnt)
115                                 cnt++
116                                 h.Reset()
117                                 h.Write(intBuf)
118                                 h.Write(buf[m*hSize : (m+1)*hSize])
119                                 neigh = bi.Uint64()
120                                 h.Write(buf[neigh*hSize : (neigh+1)*hSize])
121                                 buf = h.Sum(buf[:m*hSize])
122                         }
123                 }
124         }
125         // Extract output from buffer
126         return buf[(sCost-1)*hSize:]
127 }
128
129 // This function adds additional functionality over pure B(): ability to
130 // run several hashers (jobs) simultaneously and second-preimage resistant
131 // password double hashing.
132 //
133 //     H(p, s, jobs) = hash(p || s || (
134 //         B(p, s || "1") XOR
135 //         B(p, s || "2") XOR
136 //         B(p, s || jobs)
137 //     ))
138 func H(hasher func() hash.Hash, passwd, salt []byte, sCost, tCost int, jobs int) []byte {
139         var i int
140         results := make(chan []byte)
141         for ; i < jobs; i++ {
142                 go func(i int) {
143                         saltBuf := make([]byte, len(salt)+8)
144                         copy(saltBuf, salt)
145                         binary.BigEndian.PutUint64(saltBuf[len(salt):], uint64(i))
146                         results <- B(hasher(), passwd, saltBuf, uint64(sCost), uint64(tCost))
147                 }(i)
148         }
149         h := hasher()
150         h.Write(passwd)
151         h.Write(salt)
152         result := make([]byte, h.Size())
153         for i = 0; i < jobs; i++ {
154                 for n, e := range <-results {
155                         result[n] ^= e
156                 }
157         }
158         close(results)
159         h.Write(result)
160         return h.Sum(result[:0])
161 }