mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-05-26 21:35:16 +00:00
240 lines
8.4 KiB
Java
240 lines
8.4 KiB
Java
/*
|
||
* 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 round’s 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 round’s 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 round’s 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();
|
||
}
|
||
}
|