// balloon -- Balloon password hashing function // Copyright (C) 2016-2024 Sergey Matveev // // 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 // . // Balloon password hashing. // // Look https://crypto.stanford.edu/balloon/ for more description. package balloon import ( "encoding/binary" "hash" "math/big" ) 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 uint64) []byte { var cnt uint64 intBuf := make([]byte, 8) hSize := uint64(h.Size()) buf := make([]byte, 0, sCost*hSize) // Expand input into buffer binary.LittleEndian.PutUint64(intBuf, cnt) cnt++ h.Write(intBuf) h.Write(passwd) h.Write(salt) buf = h.Sum(buf) var m uint64 for m = 1; m < sCost; m++ { binary.LittleEndian.PutUint64(intBuf, cnt) cnt++ h.Reset() h.Write(intBuf) h.Write(buf[(m-1)*hSize : m*hSize]) buf = h.Sum(buf) } // Mix buffer contents var prev []byte var i uint64 var neigh uint64 bi := big.NewInt(0) bs := big.NewInt(int64(sCost)) biBuf := make([]byte, 0, h.Size()) for t := uint64(0); t < tCost; t++ { for m = 0; m < sCost; m++ { // Hash last and current blocks if m == 0 { prev = buf[(sCost-1)*hSize:] } else { prev = buf[(m-1)*hSize : m*hSize] } binary.LittleEndian.PutUint64(intBuf, cnt) cnt++ h.Reset() h.Write(intBuf) h.Write(prev) 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++ { h.Reset() binary.LittleEndian.PutUint64(intBuf, t) h.Write(intBuf) binary.LittleEndian.PutUint64(intBuf, m) h.Write(intBuf) binary.LittleEndian.PutUint64(intBuf, i) h.Write(intBuf) 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) binary.LittleEndian.PutUint64(intBuf, cnt) cnt++ h.Reset() h.Write(intBuf) 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)*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) // )) 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, 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() h.Write(passwd) h.Write(salt) result := make([]byte, h.Size()) for i = 0; i < jobs; i++ { for n, e := range <-results { result[n] ^= e } } close(results) h.Write(result) return h.Sum(result[:0]) }