From b146fab2c57eec9343ec7d1c69184d9af4b7c7d8 Mon Sep 17 00:00:00 2001 From: MaxKey Date: Sat, 18 Jul 2020 18:40:47 +0800 Subject: [PATCH] PasswordEncoder Delegating change support noop pbkdf2 scrypt md4 md5 sha1 sha256 sha384 sha512 sm3 ldap --- .../jdbc/DefaultJdbcAuthenticationRealm.java | 4 +- .../ApplicationAutoConfiguration.java | 37 +++++++++- .../src/main/java/org/maxkey/crypto/SM3.java | 62 ++++++++++++++++ .../org/maxkey/crypto/password/Digester.java | 48 ++++++++++++ .../crypto/password/SM3PasswordEncoder.java | 21 ++++++ .../password/StandardPasswordEncoder.java | 74 +++++++++++++++++++ .../password/SM3PasswordEncoderTest.java | 14 ++++ .../persistence/service/UserInfoService.java | 2 +- 8 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 maxkey-core/src/main/java/org/maxkey/crypto/SM3.java create mode 100644 maxkey-core/src/main/java/org/maxkey/crypto/password/Digester.java create mode 100644 maxkey-core/src/main/java/org/maxkey/crypto/password/SM3PasswordEncoder.java create mode 100644 maxkey-core/src/main/java/org/maxkey/crypto/password/StandardPasswordEncoder.java create mode 100644 maxkey-core/src/test/java/org/maxkey/crypto/password/SM3PasswordEncoderTest.java diff --git a/maxkey-core/src/main/java/org/maxkey/authn/realm/jdbc/DefaultJdbcAuthenticationRealm.java b/maxkey-core/src/main/java/org/maxkey/authn/realm/jdbc/DefaultJdbcAuthenticationRealm.java index ec883f882..53c052ec4 100644 --- a/maxkey-core/src/main/java/org/maxkey/authn/realm/jdbc/DefaultJdbcAuthenticationRealm.java +++ b/maxkey-core/src/main/java/org/maxkey/authn/realm/jdbc/DefaultJdbcAuthenticationRealm.java @@ -55,9 +55,7 @@ public class DefaultJdbcAuthenticationRealm extends AbstractAuthenticationRealm boolean passwordMatches = false; _logger.info("password : " + PasswordReciprocal.getInstance().rawPassword(userInfo.getUsername(), password)); - passwordMatches = passwordEncoder.matches( - PasswordReciprocal.getInstance().rawPassword(userInfo.getUsername(), password), - userInfo.getPassword()); + passwordMatches = passwordEncoder.matches(password,userInfo.getPassword()); _logger.debug("passwordvalid : " + passwordMatches); if (!passwordMatches) { setBadPasswordCount(userInfo); diff --git a/maxkey-core/src/main/java/org/maxkey/autoconfigure/ApplicationAutoConfiguration.java b/maxkey-core/src/main/java/org/maxkey/autoconfigure/ApplicationAutoConfiguration.java index c5410a7bf..8039066af 100644 --- a/maxkey-core/src/main/java/org/maxkey/autoconfigure/ApplicationAutoConfiguration.java +++ b/maxkey-core/src/main/java/org/maxkey/autoconfigure/ApplicationAutoConfiguration.java @@ -19,6 +19,9 @@ package org.maxkey.autoconfigure; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + import javax.sql.DataSource; import org.maxkey.authn.RealmAuthenticationProvider; import org.maxkey.authn.SavedRequestAwareAuthenticationSuccessHandler; @@ -29,6 +32,8 @@ import org.maxkey.authn.support.rememberme.RedisRemeberMeService; import org.maxkey.constants.ConstantsProperties; import org.maxkey.crypto.keystore.KeyStoreLoader; import org.maxkey.crypto.password.PasswordReciprocal; +import org.maxkey.crypto.password.SM3PasswordEncoder; +import org.maxkey.crypto.password.StandardPasswordEncoder; import org.maxkey.persistence.redis.RedisConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,7 +50,14 @@ import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.LdapShaPasswordEncoder; +import org.springframework.security.crypto.password.Md4PasswordEncoder; +import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; @Configuration @@ -120,7 +132,30 @@ public class ApplicationAutoConfiguration implements InitializingBean { */ @Bean(name = "passwordEncoder") public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + String idForEncode = "bcrypt"; + Map encoders = new HashMap(); + encoders.put(idForEncode, new BCryptPasswordEncoder()); + encoders.put("noop", NoOpPasswordEncoder.getInstance()); + encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); + encoders.put("scrypt", new SCryptPasswordEncoder()); + //md + encoders.put("md4", new Md4PasswordEncoder()); + encoders.put("md5", new MessageDigestPasswordEncoder("MD5")); + //sha + encoders.put("sha1", new StandardPasswordEncoder("SHA-1","")); + encoders.put("sha256", new StandardPasswordEncoder()); + encoders.put("sha384", new StandardPasswordEncoder("SHA-384","")); + encoders.put("sha512", new StandardPasswordEncoder("SHA-512","")); + + encoders.put("sm3", new SM3PasswordEncoder()); + + encoders.put("ldap", new LdapShaPasswordEncoder()); + + //idForEncode is default for encoder + PasswordEncoder passwordEncoder = + new DelegatingPasswordEncoder(idForEncode, encoders); + + return passwordEncoder; } /** diff --git a/maxkey-core/src/main/java/org/maxkey/crypto/SM3.java b/maxkey-core/src/main/java/org/maxkey/crypto/SM3.java new file mode 100644 index 000000000..e79e72768 --- /dev/null +++ b/maxkey-core/src/main/java/org/maxkey/crypto/SM3.java @@ -0,0 +1,62 @@ +package org.maxkey.crypto; + +import java.util.Arrays; + +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; + +/** + * SM3. + * @author Crystal.Sea + * + */ +public class SM3 { + /** + * 计算SM3摘要值 + * + * @param simple 原文 + * @return 摘要值,对于SM3算法来说是32字节 + */ + public static byte[] encode(byte[] simple) { + SM3Digest digest = new SM3Digest(); + digest.update(simple, 0, simple.length); + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + return hash; + } + + /** + * 验证摘要 + * + * @param simple 原文 + * @param cipher 摘要值 + * @return 返回true标识验证成功,false标识验证失败 + */ + public static boolean verify(byte[] simple, byte[] cipher) { + byte[] newHash = encode(simple); + if (Arrays.equals(newHash, cipher)) { + return true; + } else { + return false; + } + } + + /** + * 计算SM3 Mac值 + * + * @param key key值,可以是任意长度的字节数组 + * @param srcData 原文 + * @return Mac值,对于HMac-SM3来说是32字节 + */ + public static byte[] hmac(byte[] key, byte[] simple) { + KeyParameter keyParameter = new KeyParameter(key); + SM3Digest digest = new SM3Digest(); + HMac mac = new HMac(digest); + mac.init(keyParameter); + mac.update(simple, 0, simple.length); + byte[] result = new byte[mac.getMacSize()]; + mac.doFinal(result, 0); + return result; + } +} diff --git a/maxkey-core/src/main/java/org/maxkey/crypto/password/Digester.java b/maxkey-core/src/main/java/org/maxkey/crypto/password/Digester.java new file mode 100644 index 000000000..d5980fd81 --- /dev/null +++ b/maxkey-core/src/main/java/org/maxkey/crypto/password/Digester.java @@ -0,0 +1,48 @@ +package org.maxkey.crypto.password; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Digester { + + private final String algorithm; + + private int iterations; + + /** + * Create a new Digester. + * @param algorithm the digest algorithm; for example, "SHA-1" or "SHA-256". + * @param iterations the number of times to apply the digest algorithm to the input + */ + Digester(String algorithm, int iterations) { + // eagerly validate the algorithm + createDigest(algorithm); + this.algorithm = algorithm; + setIterations(iterations); + } + + public byte[] digest(byte[] value) { + MessageDigest messageDigest = createDigest(algorithm); + for (int i = 0; i < iterations; i++) { + value = messageDigest.digest(value); + } + return value; + } + + void setIterations(int iterations) { + if (iterations <= 0) { + throw new IllegalArgumentException("Iterations value must be greater than zero"); + } + this.iterations = iterations; + } + + private static MessageDigest createDigest(String algorithm) { + try { + return MessageDigest.getInstance(algorithm); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("No such hashing algorithm", e); + } + } +} + diff --git a/maxkey-core/src/main/java/org/maxkey/crypto/password/SM3PasswordEncoder.java b/maxkey-core/src/main/java/org/maxkey/crypto/password/SM3PasswordEncoder.java new file mode 100644 index 000000000..71cab69d7 --- /dev/null +++ b/maxkey-core/src/main/java/org/maxkey/crypto/password/SM3PasswordEncoder.java @@ -0,0 +1,21 @@ +package org.maxkey.crypto.password; + +import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.maxkey.crypto.*; + +public class SM3PasswordEncoder implements PasswordEncoder { + + @Override + public String encode(CharSequence rawPassword) { + String cipher = new String(Hex.encode(SM3.encode(rawPassword.toString().getBytes()))); + return cipher; + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + String cipher = encode(rawPassword); + return encodedPassword.equals(cipher); + } + +} diff --git a/maxkey-core/src/main/java/org/maxkey/crypto/password/StandardPasswordEncoder.java b/maxkey-core/src/main/java/org/maxkey/crypto/password/StandardPasswordEncoder.java new file mode 100644 index 000000000..a8ea87abf --- /dev/null +++ b/maxkey-core/src/main/java/org/maxkey/crypto/password/StandardPasswordEncoder.java @@ -0,0 +1,74 @@ +package org.maxkey.crypto.password; + +import static org.springframework.security.crypto.util.EncodingUtils.concatenate; +import static org.springframework.security.crypto.util.EncodingUtils.subArray; + +import java.security.MessageDigest; + +import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.crypto.codec.Utf8; +import org.springframework.security.crypto.keygen.BytesKeyGenerator; +import org.springframework.security.crypto.keygen.KeyGenerators; +import org.springframework.security.crypto.password.PasswordEncoder; + +public final class StandardPasswordEncoder implements PasswordEncoder { + + private final Digester digester; + + private final byte[] secret; + + private final BytesKeyGenerator saltGenerator; + + /** + * Constructs a standard password encoder with no additional secret value. + */ + public StandardPasswordEncoder() { + this(""); + } + + /** + * Constructs a standard password encoder with a secret value which is also included + * in the password hash. + * + * @param secret the secret key used in the encoding process (should not be shared) + */ + public StandardPasswordEncoder(CharSequence secret) { + this("SHA-256", secret); + } + + public String encode(CharSequence rawPassword) { + return encode(rawPassword, saltGenerator.generateKey()); + } + + public boolean matches(CharSequence rawPassword, String encodedPassword) { + byte[] digested = decode(encodedPassword); + byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); + return MessageDigest.isEqual(digested, digest(rawPassword, salt)); + } + + // internal helpers + + public StandardPasswordEncoder(String algorithm, CharSequence secret) { + this.digester = new Digester(algorithm, DEFAULT_ITERATIONS); + this.secret = Utf8.encode(secret); + this.saltGenerator = KeyGenerators.secureRandom(); + } + + private String encode(CharSequence rawPassword, byte[] salt) { + byte[] digest = digest(rawPassword, salt); + return new String(Hex.encode(digest)); + } + + private byte[] digest(CharSequence rawPassword, byte[] salt) { + byte[] digest = digester.digest(concatenate(salt, secret, + Utf8.encode(rawPassword))); + return concatenate(salt, digest); + } + + private byte[] decode(CharSequence encodedPassword) { + return Hex.decode(encodedPassword); + } + + private static final int DEFAULT_ITERATIONS = 1024; + +} diff --git a/maxkey-core/src/test/java/org/maxkey/crypto/password/SM3PasswordEncoderTest.java b/maxkey-core/src/test/java/org/maxkey/crypto/password/SM3PasswordEncoderTest.java new file mode 100644 index 000000000..aeea45497 --- /dev/null +++ b/maxkey-core/src/test/java/org/maxkey/crypto/password/SM3PasswordEncoderTest.java @@ -0,0 +1,14 @@ +package org.maxkey.crypto.password; + +public class SM3PasswordEncoderTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + SM3PasswordEncoder sm3 = new SM3PasswordEncoder(); + System.out.println(sm3.encode("maxkeypassword")); + + String c="f4679d46e96d95d67db4c8c91fcf8aaaa4e1d437ffee278d2ea97f41f7f48c12"; + System.out.println(sm3.matches("maxkeypassword",c)); + } + +} diff --git a/maxkey-persistence/src/main/java/org/maxkey/persistence/service/UserInfoService.java b/maxkey-persistence/src/main/java/org/maxkey/persistence/service/UserInfoService.java index c00c1c824..0b7739ba3 100644 --- a/maxkey-persistence/src/main/java/org/maxkey/persistence/service/UserInfoService.java +++ b/maxkey-persistence/src/main/java/org/maxkey/persistence/service/UserInfoService.java @@ -143,7 +143,7 @@ public class UserInfoService extends JpaBaseService { public UserInfo passwordEncoder(UserInfo userInfo) { //密码不为空,则需要进行加密处理 if(userInfo.getPassword()!=null && !userInfo.getPassword().equals("")) { - String password = passwordEncoder.encode(PasswordReciprocal.getInstance().rawPassword(userInfo.getUsername(), userInfo.getPassword())); + String password = passwordEncoder.encode(userInfo.getPassword()); userInfo.setDecipherable(ReciprocalUtils.encode(PasswordReciprocal.getInstance().rawPassword(userInfo.getUsername(), userInfo.getPassword()))); _logger.debug("decipherable : "+userInfo.getDecipherable()); userInfo.setPassword(password);