Skip to content

Commit 2e13557

Browse files
author
Winni Neessen
authored
Merge pull request #28 from wneessen/27-add-ntlm-hash-support
#27: Implement NTLM hash support for PwnedPassAPI
2 parents 2b0b51a + 179cd36 commit 2e13557

8 files changed

Lines changed: 636 additions & 12 deletions

File tree

hibp.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,18 @@ var (
4949
// expected length
5050
ErrSHA1LengthMismatch = errors.New("SHA1 hash size needs to be 160 bits")
5151

52+
// ErrNTLMLengthMismatch should be used if a given NTLM hash does not match the
53+
// expected length
54+
ErrNTLMLengthMismatch = errors.New("NTLM hash size needs to be 128 bits")
55+
5256
// ErrSHA1Invalid should be used if a given string does not represent a valid SHA1 hash
5357
ErrSHA1Invalid = errors.New("not a valid SHA1 hash")
58+
59+
// ErrNTLMInvalid should be used if a given string does not represent a valid NTLM hash
60+
ErrNTLMInvalid = errors.New("not a valid NTLM hash")
61+
62+
// ErrUnsupportedHashMode should be used if a given hash mode is not supported
63+
ErrUnsupportedHashMode = errors.New("hash mode not supported")
5464
)
5565

5666
// Client is the HIBP client object
@@ -80,7 +90,10 @@ func New(options ...Option) Client {
8090

8191
// Set defaults
8292
c.to = DefaultTimeout
83-
c.PwnedPassAPIOpts = &PwnedPasswordOptions{}
93+
c.PwnedPassAPIOpts = &PwnedPasswordOptions{
94+
HashMode: HashModeSHA1,
95+
WithPadding: false,
96+
}
8497
c.ua = DefaultUserAgent
8598

8699
// Set additional options
@@ -95,7 +108,10 @@ func New(options ...Option) Client {
95108
c.hc = httpClient(c.to)
96109

97110
// Associate the different HIBP service APIs with the Client
98-
c.PwnedPassAPI = &PwnedPassAPI{hibp: &c}
111+
c.PwnedPassAPI = &PwnedPassAPI{
112+
hibp: &c,
113+
ParamMap: make(map[string]string),
114+
}
99115
c.BreachAPI = &BreachAPI{hibp: &c}
100116
c.PasteAPI = &PasteAPI{hibp: &c}
101117

@@ -140,6 +156,18 @@ func WithRateLimitSleep() Option {
140156
}
141157
}
142158

159+
// WithPwnedNTLMHash sets the hash mode for the PwnedPasswords API to NTLM hashes
160+
//
161+
// Note: This option only affects the generic methods like PwnedPassAPI.CheckPassword
162+
// or PwnedPassAPI.ListHashesPassword. For any specifc method with the hash type in
163+
// the method name, this option is ignored and the hash type of the function is
164+
// forced
165+
func WithPwnedNTLMHash() Option {
166+
return func(c *Client) {
167+
c.PwnedPassAPIOpts.HashMode = HashModeNTLM
168+
}
169+
}
170+
143171
// HTTPReq performs an HTTP request to the corresponding API
144172
func (c *Client) HTTPReq(m, p string, q map[string]string) (*http.Request, error) {
145173
u, err := url.Parse(p)

hibp_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,25 @@ func TestNewWithHttpTimeout(t *testing.T) {
3737
func TestNewWithPwnedPadding(t *testing.T) {
3838
hc := New(WithPwnedPadding())
3939
if !hc.PwnedPassAPIOpts.WithPadding {
40-
t.Errorf("hibp client pwned padding option was not set properly. Expected %v, got: %v",
40+
t.Errorf("hibp client pwned padding option was not set properly. Expected %t, got: %t",
4141
true, hc.PwnedPassAPIOpts.WithPadding)
4242
}
4343
}
4444

45+
// TestNewWithPwnedNTLMHash tests the New() function with the PwnedPadding option
46+
func TestNewWithPwnedNTLMHash(t *testing.T) {
47+
hc := New(WithPwnedNTLMHash())
48+
if hc.PwnedPassAPIOpts.HashMode != HashModeNTLM {
49+
t.Errorf("hibp client NTLM hash mode option was not set properly. Expected %d, got: %d",
50+
HashModeNTLM, hc.PwnedPassAPIOpts.HashMode)
51+
}
52+
hc = New()
53+
if hc.PwnedPassAPIOpts.HashMode != HashModeSHA1 {
54+
t.Errorf("hibp client SHA-1 hash mode option was not set properly. Expected %d, got: %d",
55+
HashModeSHA1, hc.PwnedPassAPIOpts.HashMode)
56+
}
57+
}
58+
4559
// TestNewWithApiKey tests the New() function with the API key set
4660
func TestNewWithApiKey(t *testing.T) {
4761
apiKey := os.Getenv("HIBP_API_KEY")

md4/LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2009 The Go Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without
4+
modification, are permitted provided that the following conditions are
5+
met:
6+
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above
10+
copyright notice, this list of conditions and the following disclaimer
11+
in the documentation and/or other materials provided with the
12+
distribution.
13+
* Neither the name of Google Inc. nor the names of its
14+
contributors may be used to endorse or promote products derived from
15+
this software without specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

md4/md4.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
6+
//
7+
// NOTE: MD4 is cryptographically broken and should should only be used
8+
// where compatibility with legacy systems, not security, is the goal. Instead,
9+
// use a secure hash like SHA-256 (from crypto/sha256).
10+
package md4 // import "golang.org/x/crypto/md4"
11+
12+
import (
13+
"crypto"
14+
"hash"
15+
)
16+
17+
func init() {
18+
crypto.RegisterHash(crypto.MD4, New)
19+
}
20+
21+
// Size is the size of an MD4 checksum in bytes.
22+
const Size = 16
23+
24+
// BlockSize is the blocksize of MD4 in bytes.
25+
const BlockSize = 64
26+
27+
const (
28+
_Chunk = 64
29+
_Init0 = 0x67452301
30+
_Init1 = 0xEFCDAB89
31+
_Init2 = 0x98BADCFE
32+
_Init3 = 0x10325476
33+
)
34+
35+
// digest represents the partial evaluation of a checksum.
36+
type digest struct {
37+
s [4]uint32
38+
x [_Chunk]byte
39+
nx int
40+
len uint64
41+
}
42+
43+
func (d *digest) Reset() {
44+
d.s[0] = _Init0
45+
d.s[1] = _Init1
46+
d.s[2] = _Init2
47+
d.s[3] = _Init3
48+
d.nx = 0
49+
d.len = 0
50+
}
51+
52+
// New returns a new hash.Hash computing the MD4 checksum.
53+
func New() hash.Hash {
54+
d := new(digest)
55+
d.Reset()
56+
return d
57+
}
58+
59+
func (d *digest) Size() int { return Size }
60+
61+
func (d *digest) BlockSize() int { return BlockSize }
62+
63+
func (d *digest) Write(p []byte) (nn int, err error) {
64+
nn = len(p)
65+
d.len += uint64(nn)
66+
if d.nx > 0 {
67+
n := len(p)
68+
if n > _Chunk-d.nx {
69+
n = _Chunk - d.nx
70+
}
71+
for i := 0; i < n; i++ {
72+
d.x[d.nx+i] = p[i]
73+
}
74+
d.nx += n
75+
if d.nx == _Chunk {
76+
_Block(d, d.x[0:])
77+
d.nx = 0
78+
}
79+
p = p[n:]
80+
}
81+
n := _Block(d, p)
82+
p = p[n:]
83+
if len(p) > 0 {
84+
d.nx = copy(d.x[:], p)
85+
}
86+
return
87+
}
88+
89+
func (d *digest) Sum(in []byte) []byte {
90+
// Make a copy of d0, so that caller can keep writing and summing.
91+
dc := new(digest)
92+
*dc = *d
93+
94+
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
95+
plen := dc.len
96+
var tmp [64]byte
97+
tmp[0] = 0x80
98+
if plen%64 < 56 {
99+
_, _ = dc.Write(tmp[0 : 56-plen%64])
100+
} else {
101+
_, _ = dc.Write(tmp[0 : 64+56-plen%64])
102+
}
103+
104+
// Length in bits.
105+
plen <<= 3
106+
for i := uint(0); i < 8; i++ {
107+
tmp[i] = byte(plen >> (8 * i))
108+
}
109+
_, _ = dc.Write(tmp[0:8])
110+
111+
if dc.nx != 0 {
112+
panic("dc.nx != 0")
113+
}
114+
115+
for _, s := range dc.s {
116+
in = append(in, byte(s>>0))
117+
in = append(in, byte(s>>8))
118+
in = append(in, byte(s>>16))
119+
in = append(in, byte(s>>24))
120+
}
121+
return in
122+
}

md4/md4_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2009 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package md4
6+
7+
import (
8+
"fmt"
9+
"io"
10+
"testing"
11+
)
12+
13+
type md4Test struct {
14+
out string
15+
in string
16+
}
17+
18+
var golden = []md4Test{
19+
{"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
20+
{"bde52cb31de33e46245e05fbdbd6fb24", "a"},
21+
{"ec388dd78999dfc7cf4632465693b6bf", "ab"},
22+
{"a448017aaf21d8525fc10ae87aa6729d", "abc"},
23+
{"41decd8f579255c5200f86a4bb3ba740", "abcd"},
24+
{"9803f4a34e8eb14f96adba49064a0c41", "abcde"},
25+
{"804e7f1c2586e50b49ac65db5b645131", "abcdef"},
26+
{"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"},
27+
{"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"},
28+
{"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"},
29+
{"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"},
30+
{"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."},
31+
{"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."},
32+
{"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."},
33+
{"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
34+
{"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered. -Tom Stoppard"},
35+
{"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."},
36+
{"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."},
37+
{"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."},
38+
{"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
39+
{"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
40+
{"9ddc753e7a4ccee6081cd1b45b23a834", "size: a.out: bad magic"},
41+
{"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail. -Mark Horton"},
42+
{"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world. CCFestoon"},
43+
{"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."},
44+
{"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."},
45+
{"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."},
46+
{"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"},
47+
{"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
48+
{"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"},
49+
{"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++? -Paul Glick"},
50+
}
51+
52+
func TestGolden(t *testing.T) {
53+
for i := 0; i < len(golden); i++ {
54+
g := golden[i]
55+
c := New()
56+
for j := 0; j < 3; j++ {
57+
if j < 2 {
58+
_, _ = io.WriteString(c, g.in)
59+
} else {
60+
_, _ = io.WriteString(c, g.in[0:len(g.in)/2])
61+
c.Sum(nil)
62+
_, _ = io.WriteString(c, g.in[len(g.in)/2:])
63+
}
64+
s := fmt.Sprintf("%x", c.Sum(nil))
65+
if s != g.out {
66+
t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out)
67+
}
68+
c.Reset()
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)