From e836b4c716db7fb78ad2f69a05a0dda68dac5cc8 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Thu, 13 Apr 2017 13:33:17 +0100 Subject: [PATCH] Crypto: Add basic asymmetric encryption methods --- .../syncadapter/journalmanager/Crypto.java | 89 ++++++++++++++++++- .../journalmanager/EncryptionTest.java | 17 ++++ 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/etesync/syncadapter/journalmanager/Crypto.java b/app/src/main/java/com/etesync/syncadapter/journalmanager/Crypto.java index fa08028f..b6120c70 100644 --- a/app/src/main/java/com/etesync/syncadapter/journalmanager/Crypto.java +++ b/app/src/main/java/com/etesync/syncadapter/journalmanager/Crypto.java @@ -7,11 +7,18 @@ import com.etesync.syncadapter.utils.Base64; import org.apache.commons.codec.Charsets; import org.apache.commons.lang3.ArrayUtils; +import org.spongycastle.asn1.pkcs.PrivateKeyInfo; +import org.spongycastle.asn1.x509.SubjectPublicKeyInfo; +import org.spongycastle.crypto.AsymmetricBlockCipher; +import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.spongycastle.crypto.BufferedBlockCipher; import org.spongycastle.crypto.CipherParameters; import org.spongycastle.crypto.InvalidCipherTextException; import org.spongycastle.crypto.digests.SHA256Digest; +import org.spongycastle.crypto.encodings.OAEPEncoding; import org.spongycastle.crypto.engines.AESEngine; +import org.spongycastle.crypto.engines.RSAEngine; +import org.spongycastle.crypto.generators.RSAKeyPairGenerator; import org.spongycastle.crypto.generators.SCrypt; import org.spongycastle.crypto.macs.HMac; import org.spongycastle.crypto.modes.CBCBlockCipher; @@ -20,12 +27,21 @@ import org.spongycastle.crypto.paddings.PKCS7Padding; import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.ParametersWithIV; +import org.spongycastle.crypto.params.RSAKeyGenerationParameters; +import org.spongycastle.crypto.util.PrivateKeyFactory; +import org.spongycastle.crypto.util.PrivateKeyInfoFactory; +import org.spongycastle.crypto.util.PublicKeyFactory; +import org.spongycastle.crypto.util.SubjectPublicKeyInfoFactory; import org.spongycastle.util.encoders.Hex; +import java.io.IOException; +import java.math.BigInteger; import java.security.SecureRandom; import java.util.Arrays; +import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; public class Crypto { public static String deriveKey(String salt, String password) { @@ -34,6 +50,73 @@ public class Crypto { return Base64.encodeToString(SCrypt.generate(password.getBytes(Charsets.UTF_8), salt.getBytes(Charsets.UTF_8), 16384, 8, 1, keySize), Base64.NO_WRAP); } + public static AsymmetricKeyPair generateKeyPair() { + RSAKeyPairGenerator keyPairGenerator = new RSAKeyPairGenerator(); + keyPairGenerator.init(new RSAKeyGenerationParameters(BigInteger.valueOf(65537), new SecureRandom(), 2048, 160)); + AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair(); + try { + PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(keyPair.getPrivate()); + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(keyPair.getPublic()); + return new AsymmetricKeyPair(privateKeyInfo.getEncoded(), publicKeyInfo.getEncoded()); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + @RequiredArgsConstructor + public static class AsymmetricKeyPair { + @Getter(AccessLevel.PUBLIC) + private final byte[] privateKey; + @Getter(AccessLevel.PUBLIC) + private final byte[] publicKey; + } + + public static class AsymmetricCryptoManager { + private final AsymmetricKeyPair keyPair; + + public AsymmetricCryptoManager(AsymmetricKeyPair keyPair) { + this.keyPair = keyPair; + } + + public byte[] encrypt(byte[] pubkey, byte[] content) { + AsymmetricBlockCipher cipher = new RSAEngine(); + cipher = new OAEPEncoding(cipher); + try { + cipher.init(true, PublicKeyFactory.createKey(pubkey)); + return cipher.processBlock(content, 0, content.length); + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidCipherTextException e) { + e.printStackTrace(); + App.log.severe("Invalid ciphertext: " + Base64.encodeToString(content, Base64.NO_WRAP)); + } + + return null; + } + + public byte[] decrypt(byte[] cipherText) { + AsymmetricBlockCipher cipher = new RSAEngine(); + cipher = new OAEPEncoding(cipher); + try { + cipher.init(false, PrivateKeyFactory.createKey(keyPair.getPrivateKey())); + return cipher.processBlock(cipherText, 0, cipherText.length); + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidCipherTextException e) { + e.printStackTrace(); + App.log.severe("Invalid ciphertext: " + Base64.encodeToString(cipherText, Base64.NO_WRAP)); + } + + return null; + } + + public static byte[] getKeyFingerprint(byte[] pubkey) { + return sha256(pubkey); + } + } + public static class CryptoManager { final static int HMAC_SIZE = 256 / 8; // hmac256 in bytes @@ -146,15 +229,15 @@ public class Crypto { } static String sha256(String base) { - return sha256(base.getBytes(Charsets.UTF_8)); + return toHex(sha256(base.getBytes(Charsets.UTF_8))); } - private static String sha256(byte[] base) { + private static byte[] sha256(byte[] base) { SHA256Digest digest = new SHA256Digest(); digest.update(base, 0, base.length); byte[] ret = new byte[digest.getDigestSize()]; digest.doFinal(ret, 0); - return toHex(ret); + return ret; } static String toHex(byte[] bytes) { diff --git a/app/src/test/java/com/etesync/syncadapter/journalmanager/EncryptionTest.java b/app/src/test/java/com/etesync/syncadapter/journalmanager/EncryptionTest.java index 01883101..b0d65764 100644 --- a/app/src/test/java/com/etesync/syncadapter/journalmanager/EncryptionTest.java +++ b/app/src/test/java/com/etesync/syncadapter/journalmanager/EncryptionTest.java @@ -14,9 +14,11 @@ import org.apache.commons.codec.Charsets; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.spongycastle.util.encoders.Hex; import java.io.IOException; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; public class EncryptionTest { @@ -67,4 +69,19 @@ public class EncryptionTest { public void testCryptoVersionOutOfRange() throws Exceptions.IntegrityException, Exceptions.VersionTooNewException { new Crypto.CryptoManager(999, Helpers.keyBase64, "TestSaltShouldBeJournalId"); } + + @Test + public void testAsymCrypto() throws Exceptions.IntegrityException, Exceptions.GenericCryptoException { + Crypto.AsymmetricKeyPair keyPair = Crypto.generateKeyPair(); + Crypto.AsymmetricCryptoManager cryptoManager = new Crypto.AsymmetricCryptoManager(keyPair); + + byte[] clearText = "This Is Some Test Cleartext.".getBytes(Charsets.UTF_8); + byte[] cipher = cryptoManager.encrypt(keyPair.getPublicKey(), clearText); + byte[] clearText2 = cryptoManager.decrypt(cipher); + assertArrayEquals(clearText, clearText2); + + // Mostly for coverage. Make sure it's the expected sha256 value. + assertEquals("ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", + Hex.toHexString(Crypto.AsymmetricCryptoManager.getKeyFingerprint("a".getBytes(Charsets.UTF_8))).toLowerCase()); + } }