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$","");
}
}