org.filesys.server.auth.PasswordEncryptor Maven / Gradle / Ivy
Show all versions of jfileserver Show documentation
/*
* Copyright (C) 2006-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see .
*/
package org.filesys.server.auth;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import org.filesys.debug.Debug;
/**
* Password Encryptor Class
*
*
* Generates LanMan and NTLMv1 encrypted passwords from the plain text password and challenge key.
*
* @author GKSpencer
*/
public class PasswordEncryptor {
// Encryption algorithm types
public static final int LANMAN = 0;
public static final int NTLM1 = 1;
public static final int NTLM2 = 2;
public static final int MD4 = 3;
// Encrpytion algorithm names
private final static String[] _algNames = {"LanMan", "NTLMv1", "NTLMv2", "MD4"};
/**
* Default constructor
*/
public PasswordEncryptor() {
}
/**
* Check if the required algorithms are available
*
* @return boolean
*/
public static final boolean checkEncryptionAlgorithms() {
boolean algOK = false;
try {
// Check if MD4 is available
MessageDigest.getInstance("MD4");
// Check if DES is available
Cipher.getInstance("DES");
// Check if HMAC-MD5 is available
Mac.getInstance("HMACMD5");
// Indicate required algorithms are available
algOK = true;
}
catch (NoSuchAlgorithmException ex) {
}
catch (NoSuchPaddingException ex) {
}
// Return the encryption algorithm status
return algOK;
}
/**
* Encrypt the plain text password with the specified encryption key using the specified
* encryption algorithm.
*
* @param plainPwd Plaintext password string
* @param encryptKey byte[] Encryption key
* @param alg int Encryption algorithm
* @param userName String
* @param domain String
* @return byte[] Encrypted password
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
* @throws InvalidKeyException Key is invalid
*/
public byte[] generateEncryptedPassword(String plainPwd, byte[] encryptKey, int alg, String userName, String domain)
throws NoSuchAlgorithmException, InvalidKeyException {
// Get the password
String pwd = plainPwd;
if (pwd == null)
pwd = "";
// Determine the encryption algorithm
byte[] encPwd = null;
MessageDigest md4 = null;
int len = 0;
byte[] pwdBytes = null;
switch (alg) {
// LanMan DES encryption
case LANMAN:
encPwd = P24(pwd, encryptKey);
break;
// NTLM v1 encryption
case NTLM1:
// Create the MD4 hash
md4 = MessageDigest.getInstance("MD4");
try {
pwdBytes = pwd.getBytes("UnicodeLittleUnmarked");
}
catch (UnsupportedEncodingException ex) {
}
md4.update(pwdBytes);
byte[] p21 = new byte[21];
System.arraycopy(md4.digest(), 0, p21, 0, 16);
// Now use the LM encryption
encPwd = P24(p21, encryptKey);
break;
// NTLM v2 encryption
case NTLM2:
// Get the MD4 hash of the plaintext password
byte[] md4Hash = generateEncryptedPassword(plainPwd, encryptKey, MD4, null, null);
// HMAC-MD5 the username + domain string using the MD4 hash as the key
Mac hmacMd5 = Mac.getInstance("HMACMD5");
SecretKeySpec key = new SecretKeySpec(md4Hash, 0, md4Hash.length, "MD5");
hmacMd5.init(key);
// Build the username + domain string and convert to bytes
StringBuffer str = new StringBuffer();
str.append(userName.toUpperCase());
str.append(domain.toUpperCase());
byte[] dataByts = null;
try {
// Convert the string to a byte array
String dataStr = str.toString();
dataByts = dataStr.getBytes("UnicodeLittleUnmarked");
}
catch (UnsupportedEncodingException ex) {
}
// Encrypt the username+domain bytes to generate the NTLMv2 hash
encPwd = hmacMd5.doFinal(dataByts);
break;
// MD4 encryption
case MD4:
// Create the MD4 hash
md4 = MessageDigest.getInstance("MD4");
len = pwd.length();
pwdBytes = new byte[len * 2];
for (int i = 0; i < len; i++) {
char ch = pwd.charAt(i);
pwdBytes[i * 2] = (byte) ch;
pwdBytes[i * 2 + 1] = (byte) ((ch >> 8) & 0xFF);
}
md4.update(pwdBytes);
encPwd = new byte[16];
System.arraycopy(md4.digest(), 0, encPwd, 0, 16);
break;
}
// Return the encrypted password
return encPwd;
}
/**
* P16 encryption
*
* @param pwd java.lang.String
* @param s8 byte[]
* @return byte[]
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
*/
public final byte[] P16(String pwd, byte[] s8) throws NoSuchAlgorithmException {
// Make a 14 byte string using the password string. Truncate the
// password or pad with nulls to 14 characters.
StringBuffer p14str = new StringBuffer();
p14str.append(pwd.toUpperCase());
if (p14str.length() > 14)
p14str.setLength(14);
while (p14str.length() < 14)
p14str.append((char) 0x00);
// Convert the P14 string to an array of bytes. Allocate a 21 byte buffer as the result is
// usually passed
// through the P24() method
byte[] p14 = p14str.toString().getBytes();
byte[] p16 = new byte[21];
try {
// DES encrypt the password bytes using the challenge key
Cipher des = Cipher.getInstance("DES");
// Set the encryption seed using the first 7 bytes of the password string.
// Generate the first 8 bytes of the return value.
byte[] key = generateKey(p14, 0);
SecretKeySpec chKey = new SecretKeySpec(key, 0, key.length, "DES");
des.init(Cipher.ENCRYPT_MODE, chKey);
byte[] res = des.doFinal(s8);
System.arraycopy(res, 0, p16, 0, 8);
// Encrypt the second block
key = generateKey(p14, 7);
chKey = new SecretKeySpec(key, 0, key.length, "DES");
des.init(Cipher.ENCRYPT_MODE, chKey);
res = des.doFinal(s8);
System.arraycopy(res, 0, p16, 8, 8);
}
catch (NoSuchPaddingException ex) {
p16 = null;
}
catch (IllegalBlockSizeException ex) {
p16 = null;
}
catch (BadPaddingException ex) {
p16 = null;
}
catch (InvalidKeyException ex) {
p16 = null;
}
// Return the 16 byte encrypted value
return p16;
}
/**
* P24 DES encryption
*
* @param pwd String
* @param c8 byte[]
* @return byte[]
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
*/
private final byte[] P24(String pwd, byte[] c8) throws NoSuchAlgorithmException {
// Generate the 16 byte encrypted value using the password string and well
// known value.
byte[] s8 = "KGS!@#$%".getBytes();
byte[] p16 = P16(pwd, s8);
// Generate the 24 byte encrypted value
return P24(p16, c8);
}
/**
* P24 DES encryption
*
* @param p21 Plain password or hashed password bytes
* @param ch Challenge bytes
* @return Encrypted password
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
*/
private final byte[] P24(byte[] p21, byte[] ch) throws NoSuchAlgorithmException {
byte[] enc = null;
try {
// DES encrypt the password bytes using the challenge key
Cipher des = Cipher.getInstance("DES");
// Allocate the output bytes
enc = new byte[24];
// Encrypt the first block
byte[] key = generateKey(p21, 0);
SecretKeySpec chKey = new SecretKeySpec(key, 0, key.length, "DES");
des.init(Cipher.ENCRYPT_MODE, chKey);
byte[] res = des.doFinal(ch);
System.arraycopy(res, 0, enc, 0, 8);
// Encrypt the second block
key = generateKey(p21, 7);
chKey = new SecretKeySpec(key, 0, key.length, "DES");
des.init(Cipher.ENCRYPT_MODE, chKey);
res = des.doFinal(ch);
System.arraycopy(res, 0, enc, 8, 8);
// Encrypt the last block
key = generateKey(p21, 14);
chKey = new SecretKeySpec(key, 0, key.length, "DES");
des.init(Cipher.ENCRYPT_MODE, chKey);
res = des.doFinal(ch);
System.arraycopy(res, 0, enc, 16, 8);
}
catch (NoSuchPaddingException ex) {
Debug.println(ex);
enc = null;
}
catch (IllegalBlockSizeException ex) {
Debug.println(ex);
enc = null;
}
catch (BadPaddingException ex) {
Debug.println(ex);
enc = null;
}
catch (InvalidKeyException ex) {
Debug.println(ex);
enc = null;
}
// Return the encrypted password, or null if an error occurred
return enc;
}
/**
* Return the encryption algorithm as a string
*
* @param alg int
* @return String
*/
public static String getAlgorithmName(int alg) {
if (alg >= 0 && alg < _algNames.length)
return _algNames[alg];
return "Unknown";
}
/**
* Make a 7-byte string into a 64 bit/8 byte/longword key.
*
* @param byt byte[]
* @param off int
* @return byte[]
*/
private byte[] generateKey(byte[] byt, int off) {
// Allocate the key
byte[] key = new byte[8];
// Make a key from the input string
key[0] = (byte) (byt[off + 0] >> 1);
key[1] = (byte) (((byt[off + 0] & 0x01) << 6) | ((byt[off + 1] & 0xFF) >> 2));
key[2] = (byte) (((byt[off + 1] & 0x03) << 5) | ((byt[off + 2] & 0xFF) >> 3));
key[3] = (byte) (((byt[off + 2] & 0x07) << 4) | ((byt[off + 3] & 0xFF) >> 4));
key[4] = (byte) (((byt[off + 3] & 0x0F) << 3) | ((byt[off + 4] & 0xFF) >> 5));
key[5] = (byte) (((byt[off + 4] & 0x1F) << 2) | ((byt[off + 5] & 0xFF) >> 6));
key[6] = (byte) (((byt[off + 5] & 0x3F) << 1) | ((byt[off + 6] & 0xFF) >> 7));
key[7] = (byte) (byt[off + 6] & 0x7F);
for (int i = 0; i < 8; i++) {
key[i] = (byte) (key[i] << 1);
}
return key;
}
/**
* NTLM1 encryption of the MD4 hashed password
*
* @param p21 byte[]
* @param c8 byte[]
* @return byte[]
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
*/
public final byte[] doNTLM1Encryption(byte[] p21, byte[] c8) throws NoSuchAlgorithmException {
return P24(p21, c8);
}
/**
* NTLM2 encryption of the MD4 hashed password
*
* @param md4Hash byte[]
* @param userName String
* @param domain String
* @return byte[]
* @exception NoSuchAlgorithmException If a required encryption algorithm is not available
* @exception InvalidKeyException Invalid encryption key
*/
public final byte[] doNTLM2Encryption(byte[] md4Hash, String userName, String domain) throws NoSuchAlgorithmException,
InvalidKeyException {
// Use the MD4 hashed password as the key for HMAC-MD5
Mac hmacMd5 = Mac.getInstance("HMACMD5");
SecretKeySpec key = new SecretKeySpec(md4Hash, 0, md4Hash.length, "MD5");
hmacMd5.init(key);
// Build the data to be encrypted
StringBuffer str = new StringBuffer();
str.append(userName.toUpperCase());
// Client should sent the domain name as uppercase
// Windows 11 can send the value 'MicrosoftAccount' which is case sensitive
str.append(domain);
String dataStr = str.toString();
byte[] dataByts = null;
try {
dataByts = dataStr.getBytes("UnicodeLittleUnmarked");
}
catch (UnsupportedEncodingException ex) {
}
// Encrypt the data
return hmacMd5.doFinal(dataByts);
}
/**
* LanMan first stage of the encryption
*
* @param pwd String
* @return byte[]
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
*/
public final byte[] doLanManBaseEncryption(String pwd)
throws NoSuchAlgorithmException {
// Generate the 16 byte encrypted value using the password string and well
// known value.
byte[] s8 = "KGS!@#$%".getBytes();
return P16(pwd, s8);
}
/**
* LanMan encryption of a password previously encrypted with the well known value
*
* @param p21 byte[]
* @param c8 byte[]
* @return byte[]
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
*/
public final byte[] doLanManEncryption(byte[] p21, byte[] c8)
throws NoSuchAlgorithmException {
return P24(p21, c8);
}
/**
* MD4 password hashinh
*
* @param pwdBytes byte[]
* @return byte[]
* @throws NoSuchAlgorithmException If a required encryption algorithm is not available
* @throws InvalidKeyException Key not valid
*
*/
public final byte[] doMD4Hashing(byte[] pwdBytes)
throws NoSuchAlgorithmException, InvalidKeyException {
// Create the MD4 hash
MessageDigest md4 = MessageDigest.getInstance("MD4");
md4.update(pwdBytes);
return md4.digest();
}
}