]> Cypherpunks.ru repositories - balloon.git/blob - balloon.go
9d34db8fd261f622cea969737fef97256e34e1a0
[balloon.git] / balloon.go
1 /*
2 balloon -- Balloon password hashing function
3 Copyright (C) 2016-2023 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 delta = 3
31
32 // This function takes hash, password, salt, space cost (buffer size,
33 // number of hash-output sized blocks), time cost (number of rounds) and
34 // performs the following:
35 //
36 //      # Expand input into buffer.
37 //      buf[0] = hash(cnt++ || passwd || salt)
38 //      for m from 1 to sCost-1:
39 //          buf[m] = hash(cnt++ || buf[m-1])
40 //      # Mix buffer contents.
41 //      for t from 0 to tCost-1:
42 //          for m from 0 to sCost-1:
43 //              # Hash last and current blocks.
44 //              prev = buf[(m-1) mod sCost]
45 //              buf[m] = hash(cnt++ || prev || buf[m])
46 //              # Hash in pseudorandomly chosen blocks.
47 //              for i from 0 to delta-1:
48 //                  other = to_int(hash(cnt++ || salt || t || m || i)) mod sCost
49 //                  buf[m] = hash(cnt++ || buf[m] || buf[other])
50 //      # Extract output from buffer.
51 //      return buf[sCost-1]
52 func B(h hash.Hash, passwd, salt []byte, sCost, tCost uint64) []byte {
53         var cnt uint64
54         intBuf := make([]byte, 8)
55         hSize := uint64(h.Size())
56         buf := make([]byte, 0, sCost*hSize)
57         // Expand input into buffer
58         binary.LittleEndian.PutUint64(intBuf, cnt)
59         cnt++
60         h.Write(intBuf)
61         h.Write(passwd)
62         h.Write(salt)
63         buf = h.Sum(buf)
64         var m uint64
65         for m = 1; m < sCost; m++ {
66                 binary.LittleEndian.PutUint64(intBuf, cnt)
67                 cnt++
68                 h.Reset()
69                 h.Write(intBuf)
70                 h.Write(buf[(m-1)*hSize : m*hSize])
71                 buf = h.Sum(buf)
72         }
73         // Mix buffer contents
74         var prev []byte
75         var i uint64
76         var neigh uint64
77         bi := big.NewInt(0)
78         bs := big.NewInt(int64(sCost))
79         biBuf := make([]byte, 0, h.Size())
80         for t := uint64(0); t < tCost; t++ {
81                 for m = 0; m < sCost; m++ {
82                         // Hash last and current blocks
83                         if m == 0 {
84                                 prev = buf[(sCost-1)*hSize:]
85                         } else {
86                                 prev = buf[(m-1)*hSize : m*hSize]
87                         }
88                         binary.LittleEndian.PutUint64(intBuf, cnt)
89                         cnt++
90                         h.Reset()
91                         h.Write(intBuf)
92                         h.Write(prev)
93                         h.Write(buf[m*hSize : (m+1)*hSize])
94                         buf = h.Sum(buf[:m*hSize])
95
96                         // Hash in pseudorandomly chosen blocks
97                         for i = 0; i < delta; i++ {
98                                 h.Reset()
99                                 binary.LittleEndian.PutUint64(intBuf, t)
100                                 h.Write(intBuf)
101                                 binary.LittleEndian.PutUint64(intBuf, m)
102                                 h.Write(intBuf)
103                                 binary.LittleEndian.PutUint64(intBuf, i)
104                                 h.Write(intBuf)
105                                 biBuf = h.Sum(biBuf[:0])
106                                 binary.LittleEndian.PutUint64(intBuf, cnt)
107                                 cnt++
108                                 h.Reset()
109                                 h.Write(intBuf)
110                                 h.Write(salt)
111                                 h.Write(biBuf)
112                                 biBuf = h.Sum(biBuf[:0])
113                                 for i, j := 0, len(biBuf)-1; i < j; i, j = i+1, j-1 {
114                                         biBuf[i], biBuf[j] = biBuf[j], biBuf[i]
115                                 }
116                                 bi.SetBytes(biBuf)
117                                 bi.Mod(bi, bs)
118                                 binary.LittleEndian.PutUint64(intBuf, cnt)
119                                 cnt++
120                                 h.Reset()
121                                 h.Write(intBuf)
122                                 h.Write(buf[m*hSize : (m+1)*hSize])
123                                 neigh = bi.Uint64()
124                                 h.Write(buf[neigh*hSize : (neigh+1)*hSize])
125                                 buf = h.Sum(buf[:m*hSize])
126                         }
127                 }
128         }
129         // Extract output from buffer
130         return buf[(sCost-1)*hSize:]
131 }
132
133 // This function adds additional functionality over pure B(): ability to
134 // run several hashers (jobs) simultaneously and second-preimage resistant
135 // password double hashing.
136 //
137 //      H(p, s, jobs) = hash(p || s || (
138 //          B(p, s || "1") XOR
139 //          B(p, s || "2") XOR
140 //          B(p, s || jobs)
141 //      ))
142 func H(hasher func() hash.Hash, passwd, salt []byte, sCost, tCost int, jobs int) []byte {
143         var i int
144         results := make(chan []byte)
145         for ; i < jobs; i++ {
146                 go func(i int) {
147                         saltBuf := make([]byte, len(salt)+8)
148                         copy(saltBuf, salt)
149                         binary.LittleEndian.PutUint64(saltBuf[len(salt):], uint64(i))
150                         results <- B(hasher(), passwd, saltBuf, uint64(sCost), uint64(tCost))
151                 }(i)
152         }
153         h := hasher()
154         h.Write(passwd)
155         h.Write(salt)
156         result := make([]byte, h.Size())
157         for i = 0; i < jobs; i++ {
158                 for n, e := range <-results {
159                         result[n] ^= e
160                 }
161         }
162         close(results)
163         h.Write(result)
164         return h.Sum(result[:0])
165 }