/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare 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 Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.crypto;
import io.bitsquare.common.util.Utilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.*;
import java.util.Arrays;
// TODO is Hmac needed/make sense?
public class Encryption {
private static final Logger log = LoggerFactory.getLogger(Encryption.class);
public static final String ASYM_KEY_ALGO = "RSA";
private static final String ASYM_CIPHER = "RSA/None/OAEPWithSHA256AndMGF1Padding";
private static final String SYM_KEY_ALGO = "AES";
private static final String SYM_CIPHER = "AES";
private static final String HMAC = "HmacSHA256";
public static KeyPair generateKeyPair() {
long ts = System.currentTimeMillis();
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ASYM_KEY_ALGO, "BC");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.trace("Generate msgEncryptionKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException("Could not create key.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Symmetric
///////////////////////////////////////////////////////////////////////////////////////////
private static byte[] encrypt(byte[] payload, SecretKey secretKey) throws CryptoException {
try {
Cipher cipher = Cipher.getInstance(SYM_CIPHER, "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(payload);
} catch (Throwable e) {
e.printStackTrace();
throw new CryptoException(e);
}
}
private static byte[] decrypt(byte[] encryptedPayload, SecretKey secretKey) throws CryptoException {
try {
Cipher cipher = Cipher.getInstance(SYM_CIPHER, "BC");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(encryptedPayload);
} catch (Throwable e) {
throw new CryptoException(e);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Hmac
///////////////////////////////////////////////////////////////////////////////////////////
private static byte[] getPayloadWithHmac(byte[] payload, SecretKey secretKey) {
byte[] payloadWithHmac;
try {
ByteArrayOutputStream outputStream = null;
try {
byte[] hmac = getHmac(payload, secretKey);
outputStream = new ByteArrayOutputStream();
outputStream.write(payload);
outputStream.write(hmac);
outputStream.flush();
payloadWithHmac = outputStream.toByteArray().clone();
} catch (IOException | NoSuchProviderException e) {
e.printStackTrace();
throw new RuntimeException("Could not create hmac");
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ignored) {
}
}
}
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException("Could not create hmac");
}
return payloadWithHmac;
}
private static boolean verifyHmac(byte[] message, byte[] hmac, SecretKey secretKey) {
try {
byte[] hmacTest = getHmac(message, secretKey);
return Arrays.equals(hmacTest, hmac);
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException("Could not create cipher");
}
}
private static byte[] getHmac(byte[] payload, SecretKey secretKey) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
Mac mac = Mac.getInstance(HMAC, "BC");
mac.init(secretKey);
return mac.doFinal(payload);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Symmetric with Hmac
///////////////////////////////////////////////////////////////////////////////////////////
private static byte[] encryptPayloadWithHmac(Serializable object, SecretKey secretKey) throws CryptoException {
return encryptPayloadWithHmac(Utilities.serialize(object), secretKey);
}
private static byte[] encryptPayloadWithHmac(byte[] payload, SecretKey secretKey) throws CryptoException {
return encrypt(getPayloadWithHmac(payload, secretKey), secretKey);
}
private static byte[] decryptPayloadWithHmac(byte[] encryptedPayloadWithHmac, SecretKey secretKey) throws CryptoException {
byte[] payloadWithHmac = decrypt(encryptedPayloadWithHmac, secretKey);
String payloadWithHmacAsHex = Hex.toHexString(payloadWithHmac);
// first part is raw message
int length = payloadWithHmacAsHex.length();
int sep = length - 64;
String payloadAsHex = payloadWithHmacAsHex.substring(0, sep);
// last 64 bytes is hmac
String hmacAsHex = payloadWithHmacAsHex.substring(sep, length);
if (verifyHmac(Hex.decode(payloadAsHex), Hex.decode(hmacAsHex), secretKey)) {
return Hex.decode(payloadAsHex);
} else {
throw new CryptoException("Hmac does not match.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Asymmetric
///////////////////////////////////////////////////////////////////////////////////////////
private static byte[] encryptSecretKey(SecretKey secretKey, PublicKey publicKey) throws CryptoException {
try {
Cipher cipher = Cipher.getInstance(ASYM_CIPHER, "BC");
cipher.init(Cipher.WRAP_MODE, publicKey);
return cipher.wrap(secretKey);
} catch (Throwable e) {
e.printStackTrace();
throw new CryptoException("Couldn't encrypt payload");
}
}
private static SecretKey decryptSecretKey(byte[] encryptedSecretKey, PrivateKey privateKey) throws CryptoException {
try {
Cipher cipher = Cipher.getInstance(ASYM_CIPHER, "BC");
cipher.init(Cipher.UNWRAP_MODE, privateKey);
return (SecretKey) cipher.unwrap(encryptedSecretKey, "AES", Cipher.SECRET_KEY);
} catch (Throwable e) {
// errors when trying to decrypt foreign messages are normal
throw new CryptoException(e);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Hybrid with signature of asymmetric key
///////////////////////////////////////////////////////////////////////////////////////////
/**
* @param payload The data to encrypt.
* @param signatureKeyPair The key pair for signing.
* @param encryptionPublicKey The public key used for encryption.
* @return A SealedAndSigned object.
* @throws CryptoException
*/
public static SealedAndSigned encryptHybridWithSignature(Serializable payload, KeyPair signatureKeyPair,
PublicKey encryptionPublicKey)
throws CryptoException {
// Create a symmetric key
SecretKey secretKey = generateSecretKey();
// Encrypt secretKey with receiver's publicKey
byte[] encryptedSecretKey = encryptSecretKey(secretKey, encryptionPublicKey);
// Encrypt with sym key payload with appended hmac
byte[] encryptedPayloadWithHmac = encryptPayloadWithHmac(payload, secretKey);
// sign hash of encryptedPayloadWithHmac
byte[] hash = Hash.getHash(encryptedPayloadWithHmac);
byte[] signature = Sig.sign(signatureKeyPair.getPrivate(), hash);
// Pack all together
return new SealedAndSigned(encryptedSecretKey, encryptedPayloadWithHmac, signature, signatureKeyPair.getPublic());
}
/**
* @param sealedAndSigned The sealedAndSigned object.
* @param privateKey The private key for decryption
* @return A DecryptedPayloadWithPubKey object.
* @throws CryptoException
*/
public static DecryptedDataTuple decryptHybridWithSignature(SealedAndSigned sealedAndSigned, PrivateKey privateKey) throws CryptoException {
SecretKey secretKey = decryptSecretKey(sealedAndSigned.encryptedSecretKey, privateKey);
boolean isValid = Sig.verify(sealedAndSigned.sigPublicKey,
Hash.getHash(sealedAndSigned.encryptedPayloadWithHmac),
sealedAndSigned.signature);
if (!isValid)
throw new CryptoException("Signature verification failed.");
Serializable decryptedPayload = Utilities.deserialize(decryptPayloadWithHmac(sealedAndSigned.encryptedPayloadWithHmac, secretKey));
return new DecryptedDataTuple(decryptedPayload, sealedAndSigned.sigPublicKey);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private static SecretKey generateSecretKey() {
try {
KeyGenerator keyPairGenerator = KeyGenerator.getInstance(SYM_KEY_ALGO, "BC");
keyPairGenerator.init(256);
return keyPairGenerator.generateKey();
} catch (Throwable e) {
e.printStackTrace();
log.error(e.getMessage());
throw new RuntimeException("Couldn't generate key");
}
}
}