/*
* Copyright (C) 2014 Divide.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.divide.shared.util;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
public class Crypto {
private KeyPair keyPair;
public static KeyPair getNew() throws NoSuchAlgorithmException {
return new Crypto().keyPair;
}
private Crypto() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048, new SecureRandom());
keyPair = keyGen.generateKeyPair();
}
public PublicKey getPublicKey(){
return keyPair.getPublic();
}
public PrivateKey getPrivateKey(){
return keyPair.getPrivate();
}
public static byte[] sign(byte[] message, PrivateKey privateKey)
{
try
{
// Initialize a container for our signedMessage
byte[] signedMessage = new byte[0];
// Calculate the signature with an SHA1 hash function signed by the RSA private key
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initSign(privateKey);
sig.update(message);
byte[] signature = sig.sign();
// Add the length of the signature and the signature itself in front of the message
signedMessage = concat(signedMessage,intToByteArray(signature.length));
signedMessage = concat(signedMessage,signature);
return concat(signedMessage,message);
}
catch (GeneralSecurityException exception)
{
exception.printStackTrace();
return null;
}
}
public static byte[] encrypt(byte[] message, PublicKey publicKey)
{
try
{
// Initialize the new message container
byte[] encryptedMessage = new byte[0];
// Generate a symmetric key with the AES algorithm
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128,new SecureRandom());
SecretKey symmetricKey = keygen.generateKey();
// Wrap the symmetric key with the public key and add its length and itself to the message
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.WRAP_MODE, publicKey);
byte[] wrappedKey = cipher.wrap(symmetricKey);
encryptedMessage = concat(encryptedMessage, intToByteArray(wrappedKey.length));
encryptedMessage = concat(encryptedMessage,wrappedKey);
// Encrypt the message with the symmetric key
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, symmetricKey);
encryptedMessage = concat(encryptedMessage,cipher.doFinal(message));
return encryptedMessage;
}
catch (GeneralSecurityException exception)
{
exception.printStackTrace();
return null;
}
}
public static byte[] unsign(byte[] signedMessage, PublicKey publicKey)
{
try
{
// Read the signature from the signedMessage (and its length)
int length = byteArrayToInt(Arrays.copyOf(signedMessage, 4));
byte[] sentSignature = Arrays.copyOfRange(signedMessage,4,4+length);
// Determine the signed hash sum of the message
byte[] message = Arrays.copyOfRange(signedMessage, 4+length, signedMessage.length);
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update(message);
// Verify the signature
if (!sig.verify(sentSignature))
throw new SignatureException("Signature invalid");
return message;
}
catch (GeneralSecurityException exception)
{
exception.printStackTrace();
return null;
}
}
public static byte[] decrypt(byte[] encryptedMessage, PrivateKey privateKey)
{
try
{
// Read the symmetric key from the encrypted message (and its length)
int length = byteArrayToInt(Arrays.copyOf(encryptedMessage,4));
byte[] wrappedKey = Arrays.copyOfRange(encryptedMessage,4,4+length);
// Decrypt the symmetric key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.UNWRAP_MODE, privateKey);
Key symmetricKey = cipher.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);
// Decrypt the message and return it
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, symmetricKey);
return cipher.doFinal(Arrays.copyOfRange(encryptedMessage,4+length,encryptedMessage.length));
}
catch (GeneralSecurityException exception)
{
exception.printStackTrace();
return null;
}
}
public static PublicKey pubKeyFromBytes(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bytes));
}
public static PrivateKey priKeyFromBytes(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(bytes));
}
public static KeyPair createKeyPair(byte[] encodedPublicKey, byte[] encodedPrivateKey) {
try {
KeyFactory generator = KeyFactory.getInstance("RSA");
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
PrivateKey privateKey = generator.generatePrivate(privateKeySpec);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
PublicKey publicKey = generator.generatePublic(publicKeySpec);
return new KeyPair(publicKey, privateKey);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to create KeyPair from provided encoded keys", e);
}
}
/**
* Converts a int value into a byte array.
*
* @param value int value to be converted
* @return byte array containing the int value
*/
private static byte[] intToByteArray(int value)
{
byte[] data = new byte[4];
// int -> byte[]
for (int i = 0; i < 4; ++i)
{
int shift = i << 3; // i * 8
data[3 - i] = (byte) ((value & (0xff << shift)) >>> shift);
}
return data;
}
/**
* Converts a byte array to an int value.
*
* @param data byte array to be converted
* @return int value of the byte array
*/
private static int byteArrayToInt(byte[] data)
{
// byte[] -> int
int number = 0;
for (int i = 0; i < 4; ++i)
{
number |= (data[3-i] & 0xff) << (i << 3);
}
return number;
}
/**
* Concatenates two byte arrays and returns the resulting byte array.
*
* @param a first byte array
* @param b second byte array
* @return byte array containing first and second byte array
*/
private static byte[] concat(byte[] a, byte[] b)
{
byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}