How to read PuTTYgen RSA Keys in Java - java

I'm using PuTTYgen to generate RSA Keys and export them to the OpenSSH format. They then look like this
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,14EB795C6D5C665D
LIEmjggNbGCPMSR4XaoL3VEEbeG++Lwn/r/GjT0eGJbAaGBqH4jzpZ8ly6ZQ+2Pf
N4uLIsFgK+9a6rfM1cglvQ4IJjsw3740OFcUs4VgX4gOwqZJ6sKrrVIMCFqKfzcd
RsW9q1tFXVKXHO+OYaRY1SjGvQjZMYj1YG22njXFndmZfhC5HPba7UT7oA/p4Whu
XTbQ8r00qNjrdEG6CiK8cX2o6MITe9KITKhF/yc6snrpq222rP7P2CNGhvxLXlCI
K2KjuSHfQ5MBj9ohjEEykcyz+JYZ1E1QSR44JD025k5haC6oOYv/nnBnxAtSB6Vj
J9/ydIJUNpc9XNi3R0dS9D4wBkNUEdXMJTKkBK5zwu145soJ452YWjJ6dI/iYmic
27WRFuMln615QD0CAUUCRMFFQjA4uf/FDR+Im3glHArAWkFRElWOpuk9+ZzbcjYR
2T8ffqB80aTqCYoJ707Qc1l7VwRMBTaAk8ota0Iys2Z9PxqDDh1F+fS27GIbPNLc
2ZV12isUSOSYzyj/5R958vPMJ8ht00lCpDm81t7Yt6M3pBbUDq6QJG7fH9itrl/V
889IA2MZSt39lb/GzvMWRdraoKjw2OFSDu1Cjh7ScvMwzXF/sEI8d3Bf2bkudq/C
5t/h4Cczrlf7PQp85AvsZXL3xyGsFfvdEB0RzTg1andf55boka2ZWggJR9icW+Uh
6l9pAHfDDUaAXCJgVMV9p4IPtZyRp/OAd3vpBo5JrbpxFzoUDIIkkhhSQrANm6Hp
CtZJtQyR5OTHZUT0s3gqi+M9cgbnSEZjDPTuUzEorZWBa1fUX3+FsIMKAI7yNP8j
ALPLStDDtUJZVUtXjNdSZWH5qgsSa3xQq2a1iiaURKOMcDyELQgZbCrr+h78GSBA
XS9b3i5XkAA2g0UcVlmjY0Rm4gMbb2IUqVqGfpgs09ql60O2f6aTypz54ZvVz2f4
XrIGhlxabf+q8u6PFJNUJt33TDMSg1Y1epiu8gVngGqYIwgEJZusWYKSlS0zyzeB
FTVSFJ/7KbCmwRMQnV6gYQA5tIy1ka3Z0SRfbWH8Gu37HTd58RJmQhqXIbvZ+y9e
Irnc/mkkvsi5aaqj64/VyG1rejnssvztFMss7NjHQMr5B9exnfPaw15hU+iXnYGC
2taMxNTlxWOPW+ZTkNpCy2a2fz7URLTVRvk3U2IUqCNi4WqGxZRtJhGphdVnc6Jx
rX7KuvM2SOH0ZhxYiqlb/f0KjTEZ1XOUox/gAVmF015MFHurT66W3G8CB+zP9TVG
7DVQQRwWmSzxZOHC3+3Net2bRMoqpsdqXPRWllLBlby8f0nJMmciSbcyKYZ9lmPp
RNYuGCEKCzPNTtW0sxFKpY57odum8i7n+cW7OJbQ6ItxcEkxSvXybHOkUTXDaZK9
4qkl7Vnaw1YzGyWCW1B47XkrqFhc0Kt1XQHQsbfS9qDvt0Kh72FQLfvaJvTKLnI1
6YoEIP+x0NsbkvjfRvArfs/cdlngg1SMT18UstFqAgbVppnppGRfWti+neHRWsoj
SzvG+oJa72TMCdIekAhp1oLKDFM+jkb7CAZu+ocSpwjST4XJ0rhefA==
-----END RSA PRIVATE KEY-----
which is just an arbitrary SSH2-RSA key with the passphrase "test123" (w/o quotes).
When stored as a file, how can I read this information in Java to use it as
RSAPublicKey
and
RSAPrivateKey
Please try to be as detailed as possible, I know the very basics of cryptography but I'm far from being an expert...

You have to decode the OpenSSH key and the result will be a KeyPair
try {
String PEMPrivateKey = "...";
String password = "";
java.security.KeyPair key = PEMDecoder.decodeKeyPairOpenSSH(PEMPrivateKey, password);
java.security.interfaces.RSAPublicKey pub = (java.security.interfaces.RSAPublicKey)key.getPublic();
} catch (IOException e) {
// key not in new OpenSSH key format
}
Below is the PEM decoder from the trilead SSH project.
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import com.trilead.ssh2.crypto.cipher.AES;
import com.trilead.ssh2.crypto.cipher.BlockCipher;
import com.trilead.ssh2.crypto.cipher.CBCMode;
import com.trilead.ssh2.crypto.cipher.DES;
import com.trilead.ssh2.crypto.cipher.DESede;
import com.trilead.ssh2.crypto.digest.MD5;
import com.trilead.ssh2.signature.DSAPrivateKey;
import com.trilead.ssh2.signature.DidECKeyPair;
import com.trilead.ssh2.signature.ECDSAKeyAlgorithm;
import com.trilead.ssh2.signature.KeyAlgorithm;
import com.trilead.ssh2.signature.KeyAlgorithmManager;
import com.trilead.ssh2.signature.RSAPrivateKey;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
/**
* PEM Support.
*
* #author Christian Plattner, plattner#trilead.com
* #version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
*/
public class PEMDecoder
{
private static final int PEM_RSA_PRIVATE_KEY = 1;
private static final int PEM_DSA_PRIVATE_KEY = 2;
private static final int PEM_EC_PRIVATE_KEY = 3;
private static final int hexToInt(char c)
{
if ((c >= 'a') && (c <= 'f'))
{
return (c - 'a') + 10;
}
if ((c >= 'A') && (c <= 'F'))
{
return (c - 'A') + 10;
}
if ((c >= '0') && (c <= '9'))
{
return (c - '0');
}
throw new IllegalArgumentException("Need hex char");
}
private static byte[] hexToByteArray(String hex)
{
if (hex == null)
throw new IllegalArgumentException("null argument");
if ((hex.length() % 2) != 0)
throw new IllegalArgumentException("Uneven string length in hex encoding.");
byte decoded[] = new byte[hex.length() / 2];
for (int i = 0; i < decoded.length; i++)
{
int hi = hexToInt(hex.charAt(i * 2));
int lo = hexToInt(hex.charAt((i * 2) + 1));
decoded[i] = (byte) (hi * 16 + lo);
}
return decoded;
}
private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen)
throws IOException
{
if (salt.length < 8)
throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation.");
MD5 md5 = new MD5();
byte[] key = new byte[keyLen];
byte[] tmp = new byte[md5.getDigestLength()];
while (true)
{
md5.update(password, 0, password.length);
md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the
// salt in this step.
// This took me two hours until I got AES-xxx running.
int copy = (keyLen < tmp.length) ? keyLen : tmp.length;
md5.digest(tmp, 0);
System.arraycopy(tmp, 0, key, key.length - keyLen, copy);
keyLen -= copy;
if (keyLen == 0)
return key;
md5.update(tmp, 0, tmp.length);
}
}
private static byte[] removePadding(byte[] buff, int blockSize) throws IOException
{
/* Removes RFC 1423/PKCS #7 padding */
int rfc_1423_padding = buff[buff.length - 1] & 0xff;
if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize))
throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?");
for (int i = 2; i <= rfc_1423_padding; i++)
{
if (buff[buff.length - i] != rfc_1423_padding)
throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?");
}
byte[] tmp = new byte[buff.length - rfc_1423_padding];
System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding);
return tmp;
}
private static final PEMStructure parsePEM(char[] pem) throws IOException
{
PEMStructure ps = new PEMStructure();
String line = null;
BufferedReader br = new BufferedReader(new CharArrayReader(pem));
String endLine = null;
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, '-----BEGIN...' missing");
line = line.trim();
if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----"))
{
endLine = "-----END DSA PRIVATE KEY-----";
ps.pemType = PEM_DSA_PRIVATE_KEY;
break;
}
if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----"))
{
endLine = "-----END RSA PRIVATE KEY-----";
ps.pemType = PEM_RSA_PRIVATE_KEY;
break;
}
if (line.startsWith("-----BEGIN EC PRIVATE KEY-----"))
{
endLine = "-----END EC PRIVATE KEY-----";
ps.pemType = PEM_EC_PRIVATE_KEY;
break;
}
}
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, " + endLine + " missing");
line = line.trim();
int sem_idx = line.indexOf(':');
if (sem_idx == -1)
break;
String name = line.substring(0, sem_idx + 1);
String value = line.substring(sem_idx + 1);
String values[] = value.split(",");
for (int i = 0; i < values.length; i++)
values[i] = values[i].trim();
// Proc-Type: 4,ENCRYPTED
// DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
if ("Proc-Type:".equals(name))
{
ps.procType = values;
continue;
}
if ("DEK-Info:".equals(name))
{
ps.dekInfo = values;
continue;
}
/* Ignore line */
}
StringBuffer keyData = new StringBuffer();
while (true)
{
if (line == null)
throw new IOException("Invalid PEM structure , " + new String(pem) + endLine + " missing");
line = line.trim();
if (line.startsWith(endLine))
break;
keyData.append(line);
line = br.readLine();
}
char[] pem_chars = new char[keyData.length()];
keyData.getChars(0, pem_chars.length, pem_chars, 0);
ps.data = Base64.decode(pem_chars);
if (ps.data.length == 0)
throw new IOException("Invalid PEM structure, no data available");
return ps;
}
private static PEMStructure parsePEM(char[] pem, CertificateDecoder certificateDecoder) throws IOException
{
PEMStructure ps = new PEMStructure();
String line;
BufferedReader br = new BufferedReader(new CharArrayReader(pem));
String endLine;
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, '-----BEGIN...' missing");
line = line.trim();
if (line.startsWith(certificateDecoder.getStartLine()))
{
endLine = certificateDecoder.getEndLine();
break;
}
}
while (true)
{
line = br.readLine();
if (line == null)
throw new IOException("Invalid PEM structure, " + endLine + " missing");
line = line.trim();
int sem_idx = line.indexOf(':');
if (sem_idx == -1)
break;
String name = line.substring(0, sem_idx + 1);
String value = line.substring(sem_idx + 1);
String values[] = value.split(",");
for (int i = 0; i < values.length; i++)
values[i] = values[i].trim();
// Proc-Type: 4,ENCRYPTED
// DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
if ("Proc-Type:".equals(name))
{
ps.procType = values;
continue;
}
if ("DEK-Info:".equals(name))
{
ps.dekInfo = values;
continue;
}
/* Ignore line */
}
StringBuilder keyData = new StringBuilder();
while (true)
{
if (line == null)
throw new IOException("Invalid PEM structure, " + endLine + " missing");
line = line.trim();
if (line.startsWith(endLine))
break;
keyData.append(line);
line = br.readLine();
}
char[] pem_chars = new char[keyData.length()];
keyData.getChars(0, pem_chars.length, pem_chars, 0);
ps.data = Base64.decode(pem_chars);
if (ps.data.length == 0)
throw new IOException("Invalid PEM structure, no data available");
return ps;
}
private static final void decryptPEM(PEMStructure ps, byte[] pw) throws IOException
{
if (ps.dekInfo == null)
throw new IOException("Broken PEM, no mode and salt given, but encryption enabled");
if (ps.dekInfo.length != 2)
throw new IOException("Broken PEM, DEK-Info is incomplete!");
String algo = ps.dekInfo[0];
byte[] salt = hexToByteArray(ps.dekInfo[1]);
BlockCipher bc = null;
if (algo.equals("DES-EDE3-CBC"))
{
DESede des3 = new DESede();
des3.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24));
bc = new CBCMode(des3, salt, false);
}
else if (algo.equals("DES-CBC"))
{
DES des = new DES();
des.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 8));
bc = new CBCMode(des, salt, false);
}
else if (algo.equals("AES-128-CBC"))
{
AES aes = new AES();
aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 16));
bc = new CBCMode(aes, salt, false);
}
else if (algo.equals("AES-192-CBC"))
{
AES aes = new AES();
aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 24));
bc = new CBCMode(aes, salt, false);
}
else if (algo.equals("AES-256-CBC"))
{
AES aes = new AES();
aes.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, 32));
bc = new CBCMode(aes, salt, false);
}
else
{
throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo);
}
if ((ps.data.length % bc.getBlockSize()) != 0)
throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of "
+ bc.getBlockSize());
/* Now decrypt the content */
byte[] dz = new byte[ps.data.length];
for (int i = 0; i < ps.data.length / bc.getBlockSize(); i++)
{
bc.transformBlock(ps.data, i * bc.getBlockSize(), dz, i * bc.getBlockSize());
}
/* Now check and remove RFC 1423/PKCS #7 padding */
dz = removePadding(dz, bc.getBlockSize());
ps.data = dz;
ps.dekInfo = null;
ps.procType = null;
}
public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException
{
if (ps.procType == null)
return false;
if (ps.procType.length != 2)
throw new IOException("Unknown Proc-Type field.");
if ("4".equals(ps.procType[0]) == false)
throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")");
if ("ENCRYPTED".equals(ps.procType[1]))
return true;
return false;
}
public static Object decode(char[] pem, String password) throws IOException
{
PEMStructure ps = parsePEM(pem);
if (isPEMEncrypted(ps))
{
if (password == null)
throw new IOException("PEM is encrypted, but no password was specified");
decryptPEM(ps, password.getBytes("ISO-8859-1"));
}
if (ps.pemType == PEM_DSA_PRIVATE_KEY)
{
SimpleDERReader dr = new SimpleDERReader(ps.data);
byte[] seq = dr.readSequenceAsByteArray();
if (dr.available() != 0)
throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
dr.resetInput(seq);
BigInteger version = dr.readInt();
if (version.compareTo(BigInteger.ZERO) != 0)
throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream.");
BigInteger p = dr.readInt();
BigInteger q = dr.readInt();
BigInteger g = dr.readInt();
BigInteger y = dr.readInt();
BigInteger x = dr.readInt();
if (dr.available() != 0)
throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
return new DSAPrivateKey(p, q, g, y, x);
}
if (ps.pemType == PEM_RSA_PRIVATE_KEY)
{
SimpleDERReader dr = new SimpleDERReader(ps.data);
byte[] seq = dr.readSequenceAsByteArray();
if (dr.available() != 0)
throw new IOException("Padding in RSA PRIVATE KEY DER stream.");
dr.resetInput(seq);
BigInteger version = dr.readInt();
if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0))
throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream.");
BigInteger n = dr.readInt();
BigInteger e = dr.readInt();
BigInteger d = dr.readInt();
return new RSAPrivateKey(d, e, n);
}
if (ps.pemType == PEM_EC_PRIVATE_KEY)
{
SimpleDERReader DERderReader = new SimpleDERReader(ps.data);
byte[] sequence = DERderReader.readSequenceAsByteArray();
if (DERderReader.available() != 0) {
throw new IOException("Unexpected padding in EC private key");
}
SimpleDERReader sequenceReader = new SimpleDERReader(sequence);
BigInteger version = sequenceReader.readInt();
if ((version.compareTo(BigInteger.ONE) != 0)) {
throw new IOException("Unexpected version number in EC private key: " + version);
}
byte[] privateBytes = sequenceReader.readOctetString();
String curveOid = null;
byte[] publicBytes = null;
while (sequenceReader.available() > 0) {
int type = sequenceReader.readConstructedType();
SimpleDERReader fieldReader = sequenceReader.readConstructed();
switch (type) {
case 0:
curveOid = fieldReader.readOid();
break;
case 1:
publicBytes = fieldReader.readOctetString();
break;
}
}
ASN1ObjectIdentifier asn1 = new ASN1ObjectIdentifier(curveOid);
X9ECParameters x9 = ECNamedCurveTable.getByOID(asn1);
BigInteger s = new BigInteger(1, privateBytes);
byte[] publicBytesSlice = new byte[publicBytes.length - 1];
System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length);
ECPoint w = ECDSAKeyAlgorithm.decodePoint(publicBytesSlice, x9.getCurve());
didisoft.sftp.bouncycastle.math.ec.ECCurve curve = x9.getCurve();
ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, new didisoft.sftp.bouncycastle.jce.spec.ECParameterSpec(curve, x9.getG(), x9.getN()));
ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, new didisoft.sftp.bouncycastle.jce.spec.ECParameterSpec(curve, x9.getG(), x9.getN()));
try {
// BC hack because jce-impl KeyFactorySpi.generatePublic doesn't support java.security.spec.ECPublicKeySpec !
// KeyFactory factory = KeyFactory.getInstance("EC");
KeyFactorySpi.ECDSA factory = new KeyFactorySpi.ECDSA();
PublicKey ecPublicKey = factory.engineGeneratePublic(pubSpec);
PrivateKey ecPrivateKey = factory.engineGeneratePrivate(privSpec);
return new DidECKeyPair(ECNamedCurveTable.getName(asn1), ecPublicKey, ecPrivateKey);
} catch (InvalidKeySpecException ex) {
throw new IOException("Could not generate EC key pair: " + ex.getMessage());
}
}
throw new IOException("PEM problem: it is of unknown type");
}
public static Object decodeKeyPairOpenSSH(char[] pem, String password) throws IOException
{
KeyPair key = decodeKeyPair(pem, password);
if (key.getPrivate() instanceof java.security.interfaces.RSAPrivateKey) {
java.security.interfaces.RSAPrivateCrtKey rsa = (java.security.interfaces.RSAPrivateCrtKey)key.getPrivate();
return new RSAPrivateKey(rsa.getPrivateExponent(), rsa.getPublicExponent(), rsa.getModulus());
} else if (key.getPrivate() instanceof java.security.interfaces.DSAPrivateKey) {
java.security.interfaces.DSAPublicKey dsaPub = (java.security.interfaces.DSAPublicKey)key.getPublic();
java.security.interfaces.DSAPrivateKey dsaPriv = (java.security.interfaces.DSAPrivateKey)key.getPrivate();
return new DSAPrivateKey(dsaPub.getParams().getP(),
dsaPub.getParams().getQ(),
dsaPub.getParams().getG(),
dsaPub.getY(),
dsaPriv.getX());
} else {
return null;
}
}
/**
* Decodes keys in the new OpenSSH format
* #param pem
* #param password
* #return
* #throws IOException
*/
public static KeyPair decodeKeyPair(char[] pem, String password) throws IOException
{
for (KeyAlgorithm<PublicKey, PrivateKey> algorithm : KeyAlgorithmManager.getSupportedAlgorithms()) {
for (CertificateDecoder decoder : algorithm.getCertificateDecoders()) {
try {
PEMStructure ps = parsePEM(pem, decoder);
if (isPEMEncrypted(ps)) {
if (password == null)
throw new IOException("PEM is encrypted, but no password was specified");
decryptPEM(ps, password.getBytes("ISO-8859-1"));
}
return decoder.createKeyPair(ps, password);
} catch (IOException ex) {
//LOGGER.log(Level.FINE, "Could not decode PEM Key using current decoder: " + decoder.getClass().getName(), ex);
// we couldn't decode the input, try another decoder
}
}
}
throw new IOException("PEM problem: it is of unknown type");
}
}

Related

How to Convert "AES" key from byth[] to UTF-8 String

I need to convert secretKey.getEncoded() from byte[] to String in order to save the key to db and to send the key via message to another party.
Right now i menaged to do that in Base64 (24 bytes) but the other party can only read it in UTF-8 format (16 bytes)
I tryed to do this: String str = new String(bytes, Charsets.UTF_8);
but i get wrong Gibberish format with quation marks like this: k��$��v/~M�6�L�
What is the right way to do this?
public static String generateAESKey() throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey secretKey = kgen.generateKey();
return new String(Base64.encode(secretKey.getEncoded(), Base64.NO_WRAP));
}
Generates a key in the form of a byte arrays that you can use to encrypt and decrypt process.
private SecretKey secretKey; // member variables (in class)
public byte[] generateAESKey() {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
if (secretKey == null) {
secretKey = kgen.generateKey();
}
byte[] keyBytes = secretKey.getEncoded();
//Log.i("keyBytes", toHexString(keyBytes));
return keyBytes;
}
catch (Exception ex){
ex.printStackTrace();
}
return null;
}
result of generateAESKey:
0x04 0x90 0x74 0x21 0x73 0xB9 0x3D 0x1F 0x7B 0x19 0xC4 0x95 0x85 0x20 0xDF 0x27
Generate keys in the form of strings that you can save or send to another app.
public String getStrAESkey() {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
if (secretKey == null) {
secretKey = kgen.generateKey();
}
String keyStr = toHexString(secretKey.getEncoded());
Log.i("keyStr", keyStr);
return keyStr;
}
catch (Exception ex){
ex.printStackTrace();
}
return null;
}
result of getStrAESkey():
0490742173B93D1F7B19C4958520DF27
//
public String toHexString(byte[] bytes) {
return Utils.printHexBinary(bytes);
}
public byte[] toByteArray(String hexString) {
return Utils.parseHexBinary(hexString);
}
Utils.java
public class Utils {
private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
private static int hexToBin(char ch) {
if ('0' <= ch && ch <= '9') return ch - '0';
if ('A' <= ch && ch <= 'F') return ch - 'A' + 10;
if ('a' <= ch && ch <= 'f') return ch - 'a' + 10;
return -1;
}
public static byte[] parseHexBinary(String s) {
final int len = s.length();
if (len % 2 != 0)
throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
byte[] out = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int h = hexToBin(s.charAt(i));
int l = hexToBin(s.charAt(i + 1));
if (h == -1 || l == -1)
throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
out[i / 2] = (byte) (h * 16 + l);
}
return out;
}
public static String printHexBinary(byte[] data) {
StringBuilder r = new StringBuilder(data.length * 2);
for (byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
}

File Hash Generator in Command Prompt using File Path

I have the following Java code to generate hashes based on input text.
package main;
import java.util.Scanner;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class String_Hash_Generator {
public static void main(String args[]) throws NoSuchAlgorithmException {
Scanner inputScanner = new Scanner(System.in);
System.out.print("Input: ");
String input = inputScanner.next();
/* MD2 */
MessageDigest objMD2 = MessageDigest.getInstance("MD2");
byte[] bytMD2 = objMD2.digest(input.getBytes());
BigInteger intNumMD2 = new BigInteger(1, bytMD2);
String hcMD2 = intNumMD2.toString(16);
while (hcMD2.length() < 32) {
hcMD2 = "0" + hcMD2;
}
/* MD5 */
MessageDigest objMD5 = MessageDigest.getInstance("MD5");
byte[] bytMD5 = objMD5.digest(input.getBytes());
BigInteger intNumMD5 = new BigInteger(1, bytMD5);
String hcMD5 = intNumMD5.toString(16);
while (hcMD5.length() < 32) {
hcMD5 = "0" + hcMD5;
}
/* SHA-1 */
MessageDigest objSHA1 = MessageDigest.getInstance("SHA-1");
byte[] bytSHA1 = objSHA1.digest(input.getBytes());
BigInteger intNumSHA1 = new BigInteger(1, bytSHA1);
String hcSHA1 = intNumSHA1.toString(16);
while (hcSHA1.length() < 40) {
hcSHA1 = "0" + hcSHA1;
}
/* SHA-256 */
MessageDigest objSHA256 = MessageDigest.getInstance("SHA-256");
byte[] bytSHA256 = objSHA256.digest(input.getBytes());
BigInteger intNumSHA256 = new BigInteger(1, bytSHA256);
String hcSHA256 = intNumSHA256.toString(16);
while (hcSHA256.length() < 64) {
hcSHA256 = "0" + hcSHA256;
}
/* SHA-384 */
MessageDigest objSHA384 = MessageDigest.getInstance("SHA-384");
byte[] bytSHA384 = objSHA384.digest(input.getBytes());
BigInteger intNumSHA384 = new BigInteger(1, bytSHA384);
String hcSHA384 = intNumSHA384.toString(16);
while (hcSHA384.length() < 96) {
hcSHA384 = "0" + hcSHA384;
}
/* SHA-512 */
MessageDigest objSHA512 = MessageDigest.getInstance("SHA-512");
byte[] bytSHA512 = objSHA512.digest(input.getBytes());
BigInteger intNumSHA512 = new BigInteger(1, bytSHA512);
String hcSHA512 = intNumSHA512.toString(16);
while (hcSHA512.length() < 128) {
hcSHA512 = "0" + hcSHA512;
}
System.out.println("\nMD2: " + hcMD2
+ "\nMD5: " + hcMD5
+ "\nSHA-1: " + hcSHA1
+ "\nSHA-256: " + hcSHA256
+ "\nSHA-384: " + hcSHA384
+ "\nSHA-512: " + hcSHA512);
}
}
The input needs to be Scanner, because it is essential that it is run in a Command Prompt.
How could a file hash generator be created that takes the file path, such as C:\Program Files\WinRAR\Rar.exe and generates the hashes (MD2, MD5, SHA-1, SHA-256, SHA-384, SHA-512)?
Edit: The only solutions I have been able to find only use the file's name, not the entire path.
First, you need a mechanism to print a byte[] as hex. From the top answer to How to convert a byte array to a hex string in Java? you can do,
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
Then, you might use Files.readAllBytes(Path) to read your file(s). Finally, iterate an array of hash algorithms to calculate each hash. Something like,
public static void main(String args[]) {
Scanner inputScanner = new Scanner(System.in);
String[] hashAlgos = { "MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512" };
System.out.print("Input: ");
String input = inputScanner.next();
try {
byte[] fileContents = Files.readAllBytes(new File(input).toPath());
for (String algo : hashAlgos) {
MessageDigest md = MessageDigest.getInstance(algo);
byte[] hash = md.digest(fileContents);
System.out.printf("%s %s%n", algo, bytesToHex(hash));
}
} catch (NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
}

Potential use for data that contains its own hash?

I was writing some code to simulate the proof-of-work done by the Bitcoin network while generating blocks when I suddenly became curious: How can you create a datum that contains it's own hash?
Just for fun, I wrote a program that attempts to create data that contains it's own hash. 4 random bytes are generated, then a nonce is added at the end and the whole value is hashed with CRC32. The nonce in incremented and the process repeats until the program finds a hash that matches the original 4 bytes.
Note: The nonce can increase indefinitely.
Here's an example of the output after approx 1,980,000,000 attempts:
Found a match!
Data: 7a73a2d4ab833876
Original hash: 7a73a2d4 new hash: 7a73a2d4
Is there a potential use for this?
package selfhash;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
/**
*
* #author dylan
*/
public class SelfHash {
static byte[] data;
static byte[] hash = new byte[4];
public static void main(String[] args) {
// TODO code application logic here
SecureRandom random = new SecureRandom();
random.nextBytes(hash);
data = new byte[hash.length + 1];
System.arraycopy(hash, 0, data, 0, hash.length);
long c = 0;
while (true) {
recalculateData();
byte[] dataHash = crc32AsByteArray(data);
if (c % 10000000 == 0) {
System.out.println("Calculated " + c + " hashes");
System.out.println("Data: " + byteArrayToHex(data));
System.out.println("Original hash: " + byteArrayToHex(hash) + " new hash: " + byteArrayToHex(dataHash));
}
if (Arrays.equals(hash, dataHash)) {
System.out.println("Found a match!");
System.out.println("Data: " + byteArrayToHex(data));
System.out.println("Original hash: " + byteArrayToHex(hash) + " new hash: " + byteArrayToHex(dataHash));
break;
}
c++;
}
}
public static void recalculateData() {
int position = hash.length;
while (true) {
int valueAtPosition = unsignedToBytes(data[position]);
if (valueAtPosition == 255) {
//increase size of data
if (position == data.length-1) {
byte[] newData = new byte[data.length + 1];
System.arraycopy(data, 0, newData, 0, data.length);
data = newData;
}
data[position] = (byte) (0);
position++;
} else {
data[position] = (byte) (valueAtPosition + 1);
break;
}
}
}
public static byte[] hexToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
private static final char[] BYTE2HEX = ("000102030405060708090A0B0C0D0E0F"
+ "101112131415161718191A1B1C1D1E1F"
+ "202122232425262728292A2B2C2D2E2F"
+ "303132333435363738393A3B3C3D3E3F"
+ "404142434445464748494A4B4C4D4E4F"
+ "505152535455565758595A5B5C5D5E5F"
+ "606162636465666768696A6B6C6D6E6F"
+ "707172737475767778797A7B7C7D7E7F"
+ "808182838485868788898A8B8C8D8E8F"
+ "909192939495969798999A9B9C9D9E9F"
+ "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"
+ "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"
+ "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"
+ "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"
+ "E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"
+ "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF").toLowerCase().toCharArray();
;
public static String byteArrayToHex(byte[] bytes) {
final int len = bytes.length;
final char[] chars = new char[len << 1];
int hexIndex;
int idx = 0;
int ofs = 0;
while (ofs < len) {
hexIndex = (bytes[ofs++] & 0xFF) << 1;
chars[idx++] = BYTE2HEX[hexIndex++];
chars[idx++] = BYTE2HEX[hexIndex];
}
return new String(chars);
}
public static String sha256AsHexString(byte[] bytes) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return byteArrayToHex(digest.digest(bytes));
} catch (Exception e) {
throw new Error(e);
}
}
public static byte[] sha256AsByteArray(byte[] bytes) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return digest.digest(bytes);
} catch (Exception e) {
throw new Error(e);
}
}
public static byte[] crc32AsByteArray(byte[] bytes) {
Checksum checksum = new CRC32();
checksum.update(bytes, 0, bytes.length);
long value = checksum.getValue();
byte[] resultExcess = ByteBuffer.allocate(8).putLong(value).array();
byte[] result = new byte[4];
System.arraycopy(resultExcess, 4, result, 0, 4);
return result;
}
public static int unsignedToBytes(byte b) {
return b & 0xFF;
}
}
I can't think of a use.
CRC's are linear, and so the equations can be solved very fast to get the second four bytes. You don't need two billion trials to find it. See spoof.c.

Replicating PHP code in Java

I am attempting to translate the following code in PHP to Java.
private function _aes256_cbc_encrypt($key, $data, $iv) {
if (32 !== strlen($key))
$key = hash('SHA256', $key, true);
if (16 !== strlen($iv))
$iv = hash('MD5', $iv, true);
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
}
So far this is what I have in Java.
private String aes256_cbc_encrypt(String key, String data, String iv) throws Exception {
if (32 != key.length())
key = sha256(key);
if (16 != iv.length())
iv = md5(iv);
char padding = (char) (16-(data.length() % 16));
char[] paddingArray = new char[padding];
Arrays.fill(paddingArray, padding);
data = data + new String(paddingArray);
String mcryptData = data;
MCrypt mcrypt = new MCrypt();
String encrypted = mcrypt.bytesToHex(mcrypt.encrypt(mcryptData));
return encrypted;
}
The sha256 and md5 methods are:
public static String md5(String input) throws NoSuchAlgorithmException{
String result = input;
if(input != null) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input.getBytes());
BigInteger hash = new BigInteger(1, md.digest());
result = hash.toString(16);
while(result.length() < 32) {
result = "0" + result;
}
}
return result;
}
public static String sha256(String input) throws NoSuchAlgorithmException{
String result = input;
if(input != null) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(input.getBytes());
BigInteger hash = new BigInteger(1, md.digest());
result = hash.toString(16);
while(result.length() < 32) {
result = "0" + result;
}
}
return result;
}
And the mcrypt class I am using (which I found here https://github.com/serpro/Android-PHP-Encrypt-Decrypt/blob/master/Java/src/com/serpro/library/String/MCrypt.java) is
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
class MCrypt {
static char[] HEX_CHARS = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
private String iv = "{Yr5xwjQp0:\\EC4L";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
private String SecretKey = "m78gLMbPQ";
public MCrypt()
{
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public byte[] encrypt(String text) throws Exception
{
if(text == null || text.length() == 0)
throw new Exception("Empty string");
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e)
{
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception
{
if(code == null || code.length() == 0)
throw new Exception("Empty string");
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
//Remove trailing zeroes
if( decrypted.length > 0)
{
int trim = 0;
for( int i = decrypted.length - 1; i >= 0; i-- ) if( decrypted[i] == 0 ) trim++;
if( trim > 0 )
{
byte[] newArray = new byte[decrypted.length - trim];
System.arraycopy(decrypted, 0, newArray, 0, decrypted.length - trim);
decrypted = newArray;
}
}
} catch (Exception e)
{
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] buf)
{
char[] chars = new char[2 * buf.length];
for (int i = 0; i < buf.length; ++i)
{
chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
}
return new String(chars);
}
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
return buffer;
}
}
private static String padString(String source)
{
char paddingChar = 0;
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++)
{
source += paddingChar;
}
return source;
}
}
However the two codes give me different results. Can anyone please explain why this might be.

What is the required size of an AES key in this code?

In the code below I'm initializing the security key from an external class. Is it necessary to use the security key length of 16? Any possibility to use the smaller length byte?
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class MCrypt {
private String iv = "fedcba9876543210";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;
private String SecretKey ;
public MCrypt(String s)
{
SecretKey=s;
ivspec = new IvParameterSpec(iv.getBytes());
keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/CBC/NoPadding");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public byte[] encrypt(String text) throws Exception
{
if(text == null || text.length() == 0)
throw new Exception("Empty string");
byte[] encrypted = null;
try {
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
} catch (Exception e)
{
throw new Exception("[encrypt] " + e.getMessage());
}
return encrypted;
}
public byte[] decrypt(String code) throws Exception
{
if(code == null || code.length() == 0)
throw new Exception("Empty string");
byte[] decrypted = null;
try {
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
} catch (Exception e)
{
throw new Exception("[decrypt] " + e.getMessage());
}
return decrypted;
}
public static String bytesToHex(byte[] data)
{
if (data==null)
{
return null;
}
int len = data.length;
String str = "";
for (int i=0; i<len; i++) {
if ((data[i]&0xFF)<16)
str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
else
str = str + java.lang.Integer.toHexString(data[i]&0xFF);
}
return str;
}
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
return buffer;
}
}
private static String padString(String source)
{
char paddingChar = ' ';
int size = 16;
int x = source.length() % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++)
{
source += paddingChar;
}
return source;
}
}
Your key must match one of the permitted lengths for the AES algorithm: 16, 24 or 32 bytes. You cannot use a key smaller than 16 bytes.
Some other comments on your code:
Use camelCase for all variables (e.g. secretKey).
Avoid using a fixed IV. Generate a random IV each time and store it with the ciphertext.
Consider using a padding mode (e.g. AES/CBC/PKCS5Padding) otherwise your input must always be a multiple of 16 bytes.
You are calling getBytes() on a string without supplying a charset. This could result in different results on different platforms. Always specify a character set.
In addition to Duncan's suggested improvements:
Never ever use a password/String directly as a key!
If you want to create a key based on a password use a "Password based key derivation function" - e.g. the standard is PBKDF2 (see for example this question: PBKDF2 with bouncycastle in Java).

Categories

Resources