77 lines
1.4 KiB
Go
77 lines
1.4 KiB
Go
package utilities
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/bits-and-blooms/bloom/v3"
|
|
)
|
|
|
|
const (
|
|
// hibpHashLength is the length of a hex-encoded SHA1 hash.
|
|
hibpHashLength = 40
|
|
// hibpHashPrefixLength is the length of the hashed password prefix.
|
|
hibpHashPrefixLength = 5
|
|
)
|
|
|
|
type HIBPBloomCache struct {
|
|
sync.RWMutex
|
|
|
|
n uint
|
|
items uint
|
|
filter *bloom.BloomFilter
|
|
}
|
|
|
|
func NewHIBPBloomCache(n uint, fp float64) *HIBPBloomCache {
|
|
cache := &HIBPBloomCache{
|
|
n: n,
|
|
filter: bloom.NewWithEstimates(n, fp),
|
|
}
|
|
|
|
return cache
|
|
}
|
|
|
|
func (c *HIBPBloomCache) Cap() uint {
|
|
return c.filter.Cap()
|
|
}
|
|
|
|
func (c *HIBPBloomCache) Add(ctx context.Context, prefix []byte, suffixes [][]byte) error {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
c.items += uint(len(suffixes))
|
|
|
|
if c.items > (4*c.n)/5 {
|
|
// clear the filter if 80% full to keep the actual false
|
|
// positive rate low
|
|
c.filter.ClearAll()
|
|
|
|
// reduce memory footprint when this happens
|
|
c.filter.BitSet().Compact()
|
|
|
|
c.items = uint(len(suffixes))
|
|
}
|
|
|
|
var combined [hibpHashLength]byte
|
|
copy(combined[:], prefix)
|
|
|
|
for _, suffix := range suffixes {
|
|
copy(combined[hibpHashPrefixLength:], suffix)
|
|
|
|
c.filter.Add(combined[:])
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *HIBPBloomCache) Contains(ctx context.Context, prefix, suffix []byte) (bool, error) {
|
|
var combined [hibpHashLength]byte
|
|
copy(combined[:], prefix)
|
|
copy(combined[hibpHashPrefixLength:], suffix)
|
|
|
|
c.RLock()
|
|
defer c.RUnlock()
|
|
|
|
return c.filter.Test(combined[:]), nil
|
|
}
|