-/*
-balloon -- Balloon password hashing function
-Copyright (C) 2016-2017 Sergey Matveev <stargrave@stargrave.org>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public
-License along with this program. If not, see
-<http://www.gnu.org/licenses/>.
-*/
+// balloon -- Balloon password hashing function
+// Copyright (C) 2016-2024 Sergey Matveev <stargrave@stargrave.org>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// the Free Software Foundation, version 3 of the License.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this program. If not, see
+// <http://www.gnu.org/licenses/>.
// Balloon password hashing.
//
"math/big"
)
-const (
- delta = 3
-)
+const delta = 3
// This function takes hash, password, salt, space cost (buffer size,
// number of hash-output sized blocks), time cost (number of rounds) and
// performs the following:
//
-// # Expand input into buffer.
-// buf[0] = hash(cnt++ || passwd || salt)
-// for m from 1 to sCost-1:
-// buf[m] = hash(cnt++ || buf[m-1])
-// # Mix buffer contents.
-// for t from 0 to tCost-1:
-// for m from 0 to sCost-1:
-// # Hash last and current blocks.
-// prev = buf[(m-1) mod sCost]
-// buf[m] = hash(cnt++ || prev || buf[m])
-// # Hash in pseudorandomly chosen blocks.
-// for i from 0 to delta-1:
-// other = to_int(hash(cnt++ || salt || t || m || i)) mod sCost
-// buf[m] = hash(cnt++ || buf[m] || buf[other])
-// # Extract output from buffer.
-// return buf[sCost-1]
-func B(h hash.Hash, passwd, salt []byte, sCost, tCost int) []byte {
+// # Expand input into buffer.
+// buf[0] = hash(cnt++ || passwd || salt)
+// for m from 1 to sCost-1:
+// buf[m] = hash(cnt++ || buf[m-1])
+// # Mix buffer contents.
+// for t from 0 to tCost-1:
+// for m from 0 to sCost-1:
+// # Hash last and current blocks.
+// prev = buf[(m-1) mod sCost]
+// buf[m] = hash(cnt++ || prev || buf[m])
+// # Hash in pseudorandomly chosen blocks.
+// for i from 0 to delta-1:
+// other = to_int(hash(cnt++ || salt || t || m || i)) mod sCost
+// buf[m] = hash(cnt++ || buf[m] || buf[other])
+// # Extract output from buffer.
+// return buf[sCost-1]
+func B(h hash.Hash, passwd, salt []byte, sCost, tCost uint64) []byte {
var cnt uint64
intBuf := make([]byte, 8)
- buf := make([][]byte, sCost)
+ hSize := uint64(h.Size())
+ buf := make([]byte, 0, sCost*hSize)
// Expand input into buffer
- binary.BigEndian.PutUint64(intBuf, cnt)
+ binary.LittleEndian.PutUint64(intBuf, cnt)
cnt++
h.Write(intBuf)
h.Write(passwd)
h.Write(salt)
- buf[0] = h.Sum(nil)
- var m int
+ buf = h.Sum(buf)
+ var m uint64
for m = 1; m < sCost; m++ {
- binary.BigEndian.PutUint64(intBuf, cnt)
+ binary.LittleEndian.PutUint64(intBuf, cnt)
cnt++
h.Reset()
h.Write(intBuf)
- h.Write(buf[m-1])
- buf[m] = h.Sum(nil)
+ h.Write(buf[(m-1)*hSize : m*hSize])
+ buf = h.Sum(buf)
}
// Mix buffer contents
var prev []byte
- var i int
+ var i uint64
+ var neigh uint64
bi := big.NewInt(0)
bs := big.NewInt(int64(sCost))
biBuf := make([]byte, 0, h.Size())
- var other int
- for t := 0; t < tCost; t++ {
+ for t := uint64(0); t < tCost; t++ {
for m = 0; m < sCost; m++ {
// Hash last and current blocks
if m == 0 {
- prev = buf[len(buf)-1]
+ prev = buf[(sCost-1)*hSize:]
} else {
- prev = buf[m-1]
+ prev = buf[(m-1)*hSize : m*hSize]
}
- binary.BigEndian.PutUint64(intBuf, cnt)
+ binary.LittleEndian.PutUint64(intBuf, cnt)
cnt++
h.Reset()
h.Write(intBuf)
h.Write(prev)
- h.Write(buf[m])
- buf[m] = h.Sum(buf[m][:0])
+ h.Write(buf[m*hSize : (m+1)*hSize])
+ buf = h.Sum(buf[:m*hSize])
// Hash in pseudorandomly chosen blocks
for i = 0; i < delta; i++ {
- binary.BigEndian.PutUint64(intBuf, cnt)
- cnt++
h.Reset()
+ binary.LittleEndian.PutUint64(intBuf, t)
h.Write(intBuf)
- h.Write(salt)
- binary.BigEndian.PutUint64(intBuf, uint64(t))
+ binary.LittleEndian.PutUint64(intBuf, m)
h.Write(intBuf)
- binary.BigEndian.PutUint64(intBuf, uint64(m))
+ binary.LittleEndian.PutUint64(intBuf, i)
h.Write(intBuf)
- binary.BigEndian.PutUint64(intBuf, uint64(i))
+ biBuf = h.Sum(biBuf[:0])
+ binary.LittleEndian.PutUint64(intBuf, cnt)
+ cnt++
+ h.Reset()
h.Write(intBuf)
+ h.Write(salt)
+ h.Write(biBuf)
biBuf = h.Sum(biBuf[:0])
+ for i, j := 0, len(biBuf)-1; i < j; i, j = i+1, j-1 {
+ biBuf[i], biBuf[j] = biBuf[j], biBuf[i]
+ }
bi.SetBytes(biBuf)
bi.Mod(bi, bs)
- other = int(bi.Uint64())
- binary.BigEndian.PutUint64(intBuf, cnt)
+ binary.LittleEndian.PutUint64(intBuf, cnt)
cnt++
h.Reset()
h.Write(intBuf)
- h.Write(buf[m])
- h.Write(buf[other])
- buf[m] = h.Sum(buf[m][:0])
+ h.Write(buf[m*hSize : (m+1)*hSize])
+ neigh = bi.Uint64()
+ h.Write(buf[neigh*hSize : (neigh+1)*hSize])
+ buf = h.Sum(buf[:m*hSize])
}
}
}
// Extract output from buffer
- return buf[sCost-1]
+ return buf[(sCost-1)*hSize:]
}
// This function adds additional functionality over pure B(): ability to
// run several hashers (jobs) simultaneously and second-preimage resistant
// password double hashing.
//
-// H(p, s, jobs) = hash(p || s || (
-// B(p, s || "1") XOR
-// B(p, s || "2") XOR
-// B(p, s || jobs)
-// ))
+// H(p, s, jobs) = hash(p || s || (
+// B(p, s || "1") XOR
+// B(p, s || "2") XOR
+// B(p, s || jobs)
+// ))
func H(hasher func() hash.Hash, passwd, salt []byte, sCost, tCost int, jobs int) []byte {
var i int
results := make(chan []byte)
for ; i < jobs; i++ {
go func(i int) {
- saltBuf := make([]byte, 8)
- binary.BigEndian.PutUint64(saltBuf, uint64(i))
- results <- B(hasher(), passwd, append(salt, saltBuf...), sCost, tCost)
+ saltBuf := make([]byte, len(salt)+8)
+ copy(saltBuf, salt)
+ binary.LittleEndian.PutUint64(saltBuf[len(salt):], uint64(i))
+ results <- B(hasher(), passwd, saltBuf, uint64(sCost), uint64(tCost))
}(i)
}
h := hasher()