perlのCrypt::SaltedHashによるSSHA暗号化と、パスワード検証 - end0tknrのkipple - web写経開発
以前も似たようなエントリを記載していますが、openldap付属?のslappasswdにより生成されたSSHAなパスワードをjavaでverifyする必要が出てきた。
割りとググってみましたが、javaではperlのCrypt::SaltedHashに該当するライブラリを見つけられなかったので、Crypt::SaltedHash::validate()をjavaにre-write。
package jp.end0tknr; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.codec.binary.Base64; //////// clone of Crypt::SaltedHash::validate() from perl ///////// public class VerifyPassword { String algorithm = "SHA-1"; static Integer saltLen = 4; byte[] salt; MessageDigest md; String scheme; public static void main(String[] args) { //// $ /usr/local/openldap/sbin/slappasswd -h {SSHA} -s test0000 if(VerifyPassword.validate( "{SSHA}aHKsIEBf5t5ItDF7Yw61SUZWS2aVTjDV", "test0000") ){ System.out.println("VALID !!"); //expects VALID } else { System.out.println("IN-VALID !!"); } } public VerifyPassword ( String param_algorithm, byte[] param_salt) throws NoSuchAlgorithmException { if(param_algorithm != null && param_algorithm != ""){ algorithm = param_algorithm.toUpperCase(); } //SHA => SHA-1 , HMAC-SHA => HMAC-SHA-1 Pattern regex_p = Pattern.compile("SHA$"); Matcher regex_m = regex_p.matcher(algorithm); if(regex_m.find()){ algorithm += "-1"; } md = MessageDigest.getInstance(algorithm); salt = param_salt; scheme = makeScheme(algorithm); } static public boolean validate (String hashedData, String clearData){ hashedData = hashedData.trim(); clearData = clearData.trim(); String hashedScheme = VerifyPassword.getPassScheme(hashedData).toUpperCase(); String hashedAlgorithm = VerifyPassword.makeAlgorithm(hashedScheme); String hashedHash = VerifyPassword.getPassHash(hashedData); byte[] hashedSalt = VerifyPassword.extractSalt(hashedHash); VerifyPassword verifyPw; try { verifyPw = new VerifyPassword(hashedAlgorithm,hashedSalt); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return false; } String genHashedData = verifyPw.generate(clearData); String genHash = VerifyPassword.getPassHash(genHashedData); if( hashedHash.equals(genHash) ){ return true; } return false; } static private String getPassScheme (String hashedData){ Pattern regex_p = Pattern.compile("\\{([^\\}]*)"); Matcher regex_m = regex_p.matcher(hashedData); if(regex_m.find()){ return regex_m.group(1); } return ""; } static private String makeAlgorithm (String paramAlgorithm ){ String retAlgorithm = paramAlgorithm; Matcher regex_m = Pattern.compile("^S(.*)$").matcher(retAlgorithm); if(! regex_m.find()){ return retAlgorithm; } retAlgorithm = regex_m.group(1); Matcher regex_m2 = Pattern.compile("([a-zA-Z]+)([0-9]+)").matcher(retAlgorithm); if(regex_m2.find()){ String name = regex_m2.group(1); String digits = regex_m2.group(2); Matcher regex_m3 = Pattern.compile("^HMAC(.*)$").matcher(name); if(regex_m3.find()){ //HMAC-SHA-1 name = "HMAC-"+digits; } Matcher regex_m4 = Pattern.compile("MD$").matcher(name); if(! regex_m4.find()){ //MD2, MD4, MD5 digits= "-"+digits; } retAlgorithm = name + digits; } return retAlgorithm; } static private String getPassHash(String hashedData){ Matcher regex_m = Pattern.compile("}(.*)").matcher(hashedData); if(! regex_m.find()){ return ""; } return regex_m.group(1); } static private byte[] extractSalt(String hashedHash){ byte[] binHash = Base64.decodeBase64(hashedHash); byte[] retSalt = new byte[saltLen]; for (int i=0; i<saltLen; i++){ retSalt[i] = binHash[binHash.length-saltLen+i]; } return retSalt; } private String generate(String clearData) { MessageDigest clone; try { clone = (MessageDigest)this.md.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return ""; } byte[] saltBin = saltBin(); ByteBuffer bb = ByteBuffer.allocate(clearData.length()+saltBin.length); bb.put(clearData.getBytes()); bb.put(saltBin); clone.update( bb.array() ); byte[] tmpDigest = clone.digest(); bb = ByteBuffer.allocate(tmpDigest.length+saltBin.length); bb.put(tmpDigest); bb.put(saltBin); String saltTail = ""; try { saltTail = new String(Base64.encodeBase64(bb.array()),"UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return ""; } return "{"+ this.scheme +"}" + saltTail; } private byte[] saltBin(){ return this.salt; } private String makeScheme(String algorithm){ return "S"+ algorithm.replaceFirst("-1$",""); } }