LoginSignup
3
4

More than 5 years have passed since last update.

javaによるSaltedHashなpassword (or digest?)の検証

Posted at

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4