Skip to content

Commit 4008ff9

Browse files
committed
[CODEC-133] Add classes for MD5/SHA1/SHA-512-based Unix crypt(3) hash variants.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/codec/trunk@1328414 13f79535-47bb-0310-9956-ffa450edef68
1 parent b4c96fa commit 4008ff9

15 files changed

Lines changed: 1843 additions & 2 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ The <action> type attribute can be add,update,fix,remove.
5151
</release>
5252
-->
5353
<release version="1.7" date="TBD" description="Feature and fix release.">
54+
<action dev="ggregory" type="fix" issue="CODEC-133" due-to="lathspell">
55+
Add classes for MD5/SHA1/SHA-512-based Unix crypt(3) hash variants.
56+
</action>
5457
<action dev="ggregory" type="fix" issue="CODEC-96" due-to="sebb">
5558
Base64 encode() method is no longer thread-safe, breaking clients using it as a shared BinaryEncoder.
5659
Note: the fix breaks binary compatibility, however the changes are to a class (BaseNCodec) which is
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.codec.digest;
18+
19+
import java.util.Random;
20+
21+
/**
22+
* Base64 like method to convert binary bytes into ASCII chars.
23+
*
24+
* TODO: Can Base64 be reused?
25+
*
26+
* @version $Id $
27+
* @since 1.7
28+
*/
29+
class B64 {
30+
31+
/**
32+
* Table with characters for Base64 transformation.
33+
*/
34+
static final String B64T = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
35+
36+
/**
37+
* Base64 like conversion of bytes to ASCII chars.
38+
*
39+
* @param b2
40+
* A byte from the result.
41+
* @param b1
42+
* A byte from the result.
43+
* @param b0
44+
* A byte from the result.
45+
* @param outLen
46+
* The number of expected output chars.
47+
* @param buffer
48+
* Where the output chars is appended to.
49+
*/
50+
static void b64from24bit(byte b2, byte b1, byte b0, int outLen, StringBuilder buffer) {
51+
// The bit masking is necessary because the JVM byte type is signed!
52+
int w = ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff);
53+
// It's effectively a "for" loop but kept to resemble the original C code.
54+
int n = outLen;
55+
while (n-- > 0) {
56+
buffer.append(B64T.charAt(w & 0x3f));
57+
w >>= 6;
58+
}
59+
}
60+
61+
/**
62+
* Generates a string of random chars from the B64T set.
63+
*
64+
* @param num
65+
* Number of chars to generate.
66+
*/
67+
static String getRandomSalt(int num) {
68+
StringBuilder saltString = new StringBuilder();
69+
for (int i = 1; i <= num; i++) {
70+
saltString.append(B64T.charAt(new Random().nextInt(B64T.length())));
71+
}
72+
return saltString.toString();
73+
}
74+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.codec.digest;
18+
19+
import org.apache.commons.codec.Charsets;
20+
21+
/**
22+
* GNU libc crypt(3) compatible hash method.
23+
*
24+
* See {@link #crypt(String, String)} for further details.
25+
*
26+
* @version $Id $
27+
* @since 1.7
28+
*/
29+
public class Crypt {
30+
31+
/**
32+
* Encrypts a password in a crypt(3) compatible way.
33+
*
34+
* A random salt and the default algorithm (currently SHA-512) are used. See
35+
* {@link #crypt(String, String)} for details.
36+
*
37+
* @param keyBytes
38+
* The plaintext password.
39+
* @return The hash value.
40+
*/
41+
public static String crypt(byte[] keyBytes) throws Exception {
42+
return crypt(keyBytes, null);
43+
}
44+
45+
/**
46+
* Encrypts a password in a crypt(3) compatible way.
47+
*
48+
* A random salt and the default algorithm (currently SHA-512) are used. See
49+
* {@link #crypt(String, String)} for details.
50+
*
51+
* @param keyBytes
52+
* The plaintext password.
53+
* @param salt
54+
* The salt value
55+
* @return The hash value.
56+
*/
57+
public static String crypt(byte[] keyBytes, String salt) throws Exception {
58+
if (salt == null) {
59+
return Sha2Crypt.sha512Crypt(keyBytes);
60+
} else if (salt.startsWith(Sha2Crypt.SHA512_PREFIX)) {
61+
return Sha2Crypt.sha512Crypt(keyBytes, salt);
62+
} else if (salt.startsWith(Sha2Crypt.SHA256_PREFIX)) {
63+
return Sha2Crypt.sha256Crypt(keyBytes, salt);
64+
} else if (salt.startsWith(Md5Crypt.MD5_PREFIX)) {
65+
return Md5Crypt.md5Crypt(keyBytes, salt);
66+
} else {
67+
return UnixCrypt.crypt(keyBytes, salt);
68+
}
69+
}
70+
71+
/**
72+
* Calculates the digest using the strongest crypt(3) algorithm.
73+
*
74+
* A random salt and the default algorithm (currently SHA-512) are used.
75+
*
76+
* @see #crypt(String, String)
77+
* @param key
78+
* The plaintext password.
79+
* @return The hash value.
80+
*/
81+
public static String crypt(String key) throws Exception {
82+
return crypt(key, null);
83+
}
84+
85+
/**
86+
* Encrypts a password in a crypt(3) compatible way.
87+
*
88+
* <p>
89+
* The exact algorithm depends on the format of the salt string:
90+
* <ul>
91+
* <li>SHA-512 salts start with $6$ and are up to 16 chars long.
92+
* <li>SHA-256 salts start with $5$ and are up to 16 chars long
93+
* <li>MD5 salts start with "$1$" and are up to 8 chars long
94+
* <li>DES, the traditional UnixCrypt algorithm is used else with only 2 chars
95+
* <li>Only the first 8 chars of the passwords are used in the DES algorithm!
96+
* </ul>
97+
* The magic strings "$apr1$" and "$2a$" are not recognised by this method as its output should be identical with
98+
* that of the libc implementation.
99+
*
100+
* <p>
101+
* The rest of the salt string is drawn from the set [a-zA-Z0-9./] and is cut at the maximum length of if a "$" sign
102+
* is encountered. It is therefore valid to enter a complete hash value as salt to e.g. verify a password with:
103+
* storedPwd.equals(crypt(enteredPwd, storedPwd))
104+
*
105+
* <p>
106+
* The resulting string starts with the marker string ($6$), continues with the salt value and ends with a "$" sign
107+
* followed by the actual hash value. For DES the string only contains the salt and actual hash. It's toal length is
108+
* dependend on the algorithm used:
109+
* <ul>
110+
* <li>SHA-512: 106 chars
111+
* <li>SHA-256: 63 chars
112+
* <li>MD5: 34 chars
113+
* <li>DES: 13 chars
114+
* </ul>
115+
*
116+
* <p>
117+
* Example:
118+
*
119+
* <pre>
120+
* crypt("secret", "$1$xxxx") => "$1$xxxx$aMkevjfEIpa35Bh3G4bAc."
121+
* crypt("secret", "xx") => "xxWAum7tHdIUw"
122+
* </pre>
123+
*
124+
* This method comes in a variation that accepts a byte[] array to support input strings that are not encoded in
125+
* UTF-8 but e.g. in ISO-8859-1 where equal characters result in different byte values.
126+
*
127+
* @see "The man page of the libc crypt (3) function."
128+
* @param key
129+
* The plaintext password as entered by the used.
130+
* @param salt
131+
* The salt value
132+
* @return The hash value i.e. encrypted password including the salt string
133+
*/
134+
public static String crypt(String key, String salt) throws Exception {
135+
return crypt(key.getBytes(Charsets.UTF_8), salt);
136+
}
137+
}

0 commit comments

Comments
 (0)