1// Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>
2// Copyright 2013, Jonas mg
3// All rights reserved.
4//
5// Use of this source code is governed by a BSD-style license
6// that can be found in the LICENSE file.
7
8// Package md5_crypt implements the standard Unix MD5-crypt algorithm created by
9// Poul-Henning Kamp for FreeBSD.
10package md5_crypt
11
12import (
13	"bytes"
14	"crypto/md5"
15
16	"github.com/kless/osutil/user/crypt"
17	"github.com/kless/osutil/user/crypt/common"
18)
19
20func init() {
21	crypt.RegisterCrypt(crypt.MD5, New, MagicPrefix)
22}
23
24// NOTE: Cisco IOS only allows salts of length 4.
25
26const (
27	MagicPrefix   = "$1$"
28	SaltLenMin    = 1 // Real minimum is 0, but that isn't useful.
29	SaltLenMax    = 8
30	RoundsDefault = 1000
31)
32
33type crypter struct{ Salt common.Salt }
34
35// New returns a new crypt.Crypter computing the MD5-crypt password hashing.
36func New() crypt.Crypter {
37	return &crypter{GetSalt()}
38}
39
40func (c *crypter) Generate(key, salt []byte) (string, error) {
41	if len(salt) == 0 {
42		salt = c.Salt.Generate(SaltLenMax)
43	}
44	if !bytes.HasPrefix(salt, c.Salt.MagicPrefix) {
45		return "", common.ErrSaltPrefix
46	}
47
48	saltToks := bytes.Split(salt, []byte{'$'})
49
50	if len(saltToks) < 3 {
51		return "", common.ErrSaltFormat
52	} else {
53		salt = saltToks[2]
54	}
55	if len(salt) > 8 {
56		salt = salt[0:8]
57	}
58
59	// Compute alternate MD5 sum with input KEY, SALT, and KEY.
60	Alternate := md5.New()
61	Alternate.Write(key)
62	Alternate.Write(salt)
63	Alternate.Write(key)
64	AlternateSum := Alternate.Sum(nil) // 16 bytes
65
66	A := md5.New()
67	A.Write(key)
68	A.Write(c.Salt.MagicPrefix)
69	A.Write(salt)
70	// Add for any character in the key one byte of the alternate sum.
71	i := len(key)
72	for ; i > 16; i -= 16 {
73		A.Write(AlternateSum)
74	}
75	A.Write(AlternateSum[0:i])
76
77	// The original implementation now does something weird:
78	//   For every 1 bit in the key, the first 0 is added to the buffer
79	//   For every 0 bit, the first character of the key
80	// This does not seem to be what was intended but we have to follow this to
81	// be compatible.
82	for i = len(key); i > 0; i >>= 1 {
83		if (i & 1) == 0 {
84			A.Write(key[0:1])
85		} else {
86			A.Write([]byte{0})
87		}
88	}
89	Csum := A.Sum(nil)
90
91	// In fear of password crackers here comes a quite long loop which just
92	// processes the output of the previous round again.
93	// We cannot ignore this here.
94	for i = 0; i < RoundsDefault; i++ {
95		C := md5.New()
96
97		// Add key or last result.
98		if (i & 1) != 0 {
99			C.Write(key)
100		} else {
101			C.Write(Csum)
102		}
103		// Add salt for numbers not divisible by 3.
104		if (i % 3) != 0 {
105			C.Write(salt)
106		}
107		// Add key for numbers not divisible by 7.
108		if (i % 7) != 0 {
109			C.Write(key)
110		}
111		// Add key or last result.
112		if (i & 1) == 0 {
113			C.Write(key)
114		} else {
115			C.Write(Csum)
116		}
117
118		Csum = C.Sum(nil)
119	}
120
121	out := make([]byte, 0, 23+len(c.Salt.MagicPrefix)+len(salt))
122	out = append(out, c.Salt.MagicPrefix...)
123	out = append(out, salt...)
124	out = append(out, '$')
125	out = append(out, common.Base64_24Bit([]byte{
126		Csum[12], Csum[6], Csum[0],
127		Csum[13], Csum[7], Csum[1],
128		Csum[14], Csum[8], Csum[2],
129		Csum[15], Csum[9], Csum[3],
130		Csum[5], Csum[10], Csum[4],
131		Csum[11],
132	})...)
133
134	// Clean sensitive data.
135	A.Reset()
136	Alternate.Reset()
137	for i = 0; i < len(AlternateSum); i++ {
138		AlternateSum[i] = 0
139	}
140
141	return string(out), nil
142}
143
144func (c *crypter) Verify(hashedKey string, key []byte) error {
145	newHash, err := c.Generate(key, []byte(hashedKey))
146	if err != nil {
147		return err
148	}
149	if newHash != hashedKey {
150		return crypt.ErrKeyMismatch
151	}
152	return nil
153}
154
155func (c *crypter) Cost(hashedKey string) (int, error) { return RoundsDefault, nil }
156
157func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt }
158
159func GetSalt() common.Salt {
160	return common.Salt{
161		MagicPrefix:   []byte(MagicPrefix),
162		SaltLenMin:    SaltLenMin,
163		SaltLenMax:    SaltLenMax,
164		RoundsDefault: RoundsDefault,
165	}
166}
167