1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.springframework.security.providers.ldap.authenticator;
17
18 import org.springframework.security.providers.encoding.PasswordEncoder;
19 import org.springframework.security.providers.encoding.ShaPasswordEncoder;
20
21 import org.apache.commons.codec.binary.Base64;
22
23 import org.springframework.util.Assert;
24
25 import java.io.UnsupportedEncodingException;
26 import java.security.MessageDigest;
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public class LdapShaPasswordEncoder implements PasswordEncoder {
41
42
43
44 private static final int SHA_LENGTH = 20;
45 private static final String SSHA_PREFIX = "{SSHA}";
46 private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase();
47 private static final String SHA_PREFIX = "{SHA}";
48 private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
49
50
51 private boolean forceLowerCasePrefix;
52
53
54
55 public LdapShaPasswordEncoder() {}
56
57
58
59 private byte[] combineHashAndSalt(byte[] hash, byte[] salt) {
60 if (salt == null) {
61 return hash;
62 }
63
64 byte[] hashAndSalt = new byte[hash.length + salt.length];
65 System.arraycopy(hash, 0, hashAndSalt, 0, hash.length);
66 System.arraycopy(salt, 0, hashAndSalt, hash.length, salt.length);
67
68 return hashAndSalt;
69 }
70
71
72
73
74
75
76
77
78
79
80
81 public String encodePassword(String rawPass, Object salt) {
82 MessageDigest sha;
83
84 try {
85 sha = MessageDigest.getInstance("SHA");
86 sha.update(rawPass.getBytes("UTF-8"));
87 } catch (java.security.NoSuchAlgorithmException e) {
88 throw new IllegalStateException("No SHA implementation available!");
89 } catch (UnsupportedEncodingException ue) {
90 throw new IllegalStateException("UTF-8 not supported!");
91 }
92
93 if (salt != null) {
94 Assert.isInstanceOf(byte[].class, salt, "Salt value must be a byte array");
95 sha.update((byte[]) salt);
96 }
97
98 byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt);
99
100 String prefix;
101
102 if (salt == null) {
103 prefix = forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX;
104 } else {
105 prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX;
106 }
107
108 return prefix + new String(Base64.encodeBase64(hash));
109 }
110
111 private byte[] extractSalt(String encPass) {
112 String encPassNoLabel = encPass.substring(6);
113
114 byte[] hashAndSalt = Base64.decodeBase64(encPassNoLabel.getBytes());
115 int saltLength = hashAndSalt.length - SHA_LENGTH;
116 byte[] salt = new byte[saltLength];
117 System.arraycopy(hashAndSalt, SHA_LENGTH, salt, 0, saltLength);
118
119 return salt;
120 }
121
122
123
124
125
126
127
128
129
130
131
132 public boolean isPasswordValid(final String encPass, final String rawPass, Object salt) {
133 String prefix = extractPrefix(encPass);
134
135 if (prefix == null) {
136 return encPass.equals(rawPass);
137 }
138
139 if (prefix.equals(SSHA_PREFIX) || prefix.equals(SSHA_PREFIX_LC)) {
140 salt = extractSalt(encPass);
141 } else if (!prefix.equals(SHA_PREFIX) && !prefix.equals(SHA_PREFIX_LC)) {
142 throw new IllegalArgumentException("Unsupported password prefix '" + prefix + "'");
143 } else {
144
145 salt = null;
146 }
147
148 int startOfHash = prefix.length() + 1;
149
150 String encodedRawPass = encodePassword(rawPass, salt).substring(startOfHash);
151
152 return encodedRawPass.equals(encPass.substring(startOfHash));
153 }
154
155
156
157
158 private String extractPrefix(String encPass) {
159 if (!encPass.startsWith("{")) {
160 return null;
161 }
162
163 int secondBrace = encPass.lastIndexOf('}');
164
165 if (secondBrace < 0) {
166 throw new IllegalArgumentException("Couldn't find closing brace for SHA prefix");
167 }
168
169 return encPass.substring(0, secondBrace + 1);
170 }
171
172 public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) {
173 this.forceLowerCasePrefix = forceLowerCasePrefix;
174 }
175 }