Files
jpexs-decompiler/libsrc/ffdec_lib/src/com/jpexs/helpers/MD5Crypt.java
2024-08-08 19:27:14 +02:00

240 lines
8.4 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2010-2024 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.helpers;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
/**
* MD5 crypt - based on passlib.hash.md5_crypt.
*
* @author JPEXS
*/
public class MD5Crypt {
private static final String SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
private static final String HASH64_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static final String MAGIC = "$1$";
public static final String MAGIC_APACHE = "$apr1$";
public static boolean checkPassword(String password, String hash) {
String magic;
if (hash.startsWith(MAGIC)) {
magic = MAGIC;
} else if (hash.startsWith(MAGIC_APACHE)) {
magic = MAGIC_APACHE;
} else {
return false;
}
String checksum = hash.substring(magic.length());
String salt = "";
if (checksum.contains("$")) {
salt = checksum.substring(0, checksum.indexOf('$'));
}
return hash.equals(crypt(password, salt, magic));
}
public static String generateSalt(int length) {
Random rnd = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(SALT_CHARS.charAt(rnd.nextInt(SALT_CHARS.length())));
}
return sb.toString();
}
public static String cryptApache(String password, int saltLength) {
return crypt(password, generateSalt(saltLength), MAGIC_APACHE);
}
public static String cryptApache(String password, String salt) {
return crypt(password, salt, MAGIC_APACHE);
}
public static String crypt(String password, int saltLength, String magic) {
return crypt(password, generateSalt(saltLength), magic);
}
public static String crypt(String password, int saltLength) {
return crypt(password, generateSalt(saltLength), MAGIC);
}
public static String crypt(String password, String salt) {
return crypt(password, salt, MAGIC);
}
private static String crypt(String password, String salt, String magic) {
if (salt.startsWith(magic)) {
salt = salt.substring(magic.length());
}
if (salt.length() > 8) {
salt = salt.substring(0, 8);
}
byte[] passwordBytes = password.getBytes(Utf8Helper.charset);
byte[] saltBytes = salt.getBytes(Utf8Helper.charset);
byte[] constBytes = magic.getBytes(Utf8Helper.charset);
MessageDigest b;
try {
b = MessageDigest.getInstance("MD5"); //Start MD5 digest B
} catch (NoSuchAlgorithmException ex) {
return null;
}
b.update(passwordBytes); //Add the password to digest B
b.update(saltBytes); //Add the salt to digest B
b.update(passwordBytes); //Add the password to digest B
byte[] digest_b = b.digest(); //Finish MD5 digest B
MessageDigest a;
try {
a = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ex) {
return null;
}
a.update(passwordBytes); //Add the password to digest A
a.update(constBytes); //Add the constant string $1$ to digest A
a.update(saltBytes); //Add the salt to digest A
//For each block of 16 bytes in the password string, add digest B to digest A
for (int i = passwordBytes.length; i > 0; i -= 16) {
if (i >= 16) {
a.update(digest_b);
} else { //For the remaining N bytes of the password string, add the first N bytes of digest B to digest A
a.update(digest_b, 0, i);
}
}
/*
For each bit in the binary representation of the length of the password string; starting with the lowest value bit, up to and including the largest-valued bit that is set to 1:
If the current bit is set 0 (!! not 1), add the first character of the password to digest A.
Otherwise, add a NULL character to digest A.
(If the password is the empty string, step 14 is omitted entirely).
*/
for (int i = passwordBytes.length; i > 0; i = i >>> 1) {
if ((i & 1) == 0) {
a.update(passwordBytes, 0, 1);
} else {
a.update((byte) 0);
}
}
byte[] digest_a = a.digest(); //Finish MD5 digest A
byte[] round_result = null;
//For 1000 rounds (round values 0..999 inclusive)
for (int round = 0; round < 1000; round++) {
MessageDigest c;
try {
c = MessageDigest.getInstance("MD5"); //Start MD5 digest C
} catch (NoSuchAlgorithmException ex) {
return null;
}
//If the round is odd, add the password to digest C
if (round % 2 == 1) {
c.update(passwordBytes);
}
//If the round is even, add the previous rounds result to digest C (for round 0, add digest A instead).
if (round % 2 == 0) {
if (round == 0) {
c.update(digest_a);
} else {
c.update(round_result);
}
}
//If the round is not a multiple of 3, add the salt to digest C.
if (round % 3 != 0) {
c.update(saltBytes);
}
//If the round is not a multiple of 7, add the password to digest C.
if (round % 7 != 0) {
c.update(passwordBytes);
}
//If the round is even, add the password to digest C.
if (round % 2 == 0) {
c.update(passwordBytes);
}
//If the round is odd, add the previous rounds result to digest C (for round 0, add digest A instead).
if (round % 2 == 1) {
if (round == 0) {
c.update(digest_a);
} else {
c.update(round_result);
}
}
//Use the final value of MD5 digest C as the result for this round.
round_result = c.digest();
}
//Transpose the 16 bytes of the final rounds result in the following order:
//12,6,0,13,7,1,14,8,2,15,9,3,5,10,4,11
byte[] transposed = new byte[]{
round_result[12],
round_result[6],
round_result[0],
round_result[13],
round_result[7],
round_result[1],
round_result[14],
round_result[8],
round_result[2],
round_result[15],
round_result[9],
round_result[3],
round_result[5],
round_result[10],
round_result[4],
round_result[11]};
//Encode the resulting 16 byte string into a 22 character hash64-encoded string
//(the 2 msb bits encoded by the last hash64 character are used as 0 padding).
StringBuilder result = new StringBuilder();
int dstCharRem = 22;
for (int srcBytePos = 0; srcBytePos < transposed.length; srcBytePos += 3, dstCharRem -= 4) {
long v = (transposed[srcBytePos] & 0xff);
if (srcBytePos + 1 < transposed.length) {
v |= ((transposed[srcBytePos + 1] & 0xff) << 8);
}
if (srcBytePos + 2 < transposed.length) {
v |= ((transposed[srcBytePos + 2] & 0xff) << 16);
}
for (int j = 0; j < (dstCharRem >= 4 ? 4 : dstCharRem); j++) {
result.append(HASH64_CHARS.charAt((int) (v & 0x3f)));
v >>>= 6;
}
}
return magic + salt + "$" + result.toString();
}
}