1 package civitas.crypto;
2
3 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4 import static org.junit.jupiter.api.Assertions.assertEquals;
5 import static org.junit.jupiter.api.Assertions.assertFalse;
6 import static org.junit.jupiter.api.Assertions.assertThrows;
7 import static org.junit.jupiter.api.Assertions.assertTrue;
8
9 import java.security.KeyPair;
10 import java.security.PrivateKey;
11 import java.security.PublicKey;
12 import java.security.SecureRandom;
13 import java.security.spec.InvalidKeySpecException;
14 import java.security.spec.PKCS8EncodedKeySpec;
15 import java.security.spec.X509EncodedKeySpec;
16 import java.util.Random;
17 import java.util.stream.IntStream;
18
19 import javax.crypto.Cipher;
20 import javax.crypto.SecretKey;
21 import javax.crypto.spec.SecretKeySpec;
22
23 import org.junit.jupiter.api.DisplayName;
24 import org.junit.jupiter.api.Test;
25 import org.mockito.InjectMocks;
26
27 import civitas.util.BasicValuesTestData;
28 import civitas.util.CivitasBigInteger;
29 import io.github.magwas.testing.TestBase;
30
31 class CryptoBaseTest extends TestBase implements Constants, BasicValuesTestData {
32
33 @InjectMocks
34 CryptoBase cryptoBase;
35
36 @Test
37 @DisplayName("we can generate a shared key, create an identical key "
38 + "using the shared key factory, and when we encrypt some bytes "
39 + "with the first key and decrypt it with the second one, we get our bytes back")
40 void test() throws InvalidKeySpecException {
41
42 SecretKey sharedKey =
43 cryptoBase.getSharedKeyGenerator(SHARED_KEY_LENGTH).generateKey();
44 SecretKeySpec skeySpec = new SecretKeySpec(sharedKey.getEncoded(), SHARED_KEY_ALG);
45
46 SecretKey sharedKey2 = cryptoBase.sharedKeyFactory.generateSecret(skeySpec);
47
48 byte[] encrypted =
49 cryptoBase.doCrypto(SHARED_KEY_ALG, SHARED_KEY_PROVIDER, sharedKey, Cipher.ENCRYPT_MODE, BYTES);
50
51 byte[] decrypted =
52 cryptoBase.doCrypto(SHARED_KEY_ALG, SHARED_KEY_PROVIDER, sharedKey2, Cipher.DECRYPT_MODE, encrypted);
53
54 assertArrayEquals(BYTES, decrypted);
55 }
56
57 @Test
58 @DisplayName("getSharedKeyGenerator caches its result")
59 void test4() {
60 assertEquals(
61 cryptoBase.getSharedKeyGenerator(SHARED_KEY_LENGTH),
62 cryptoBase.getSharedKeyGenerator(SHARED_KEY_LENGTH));
63 }
64
65 @Test
66 @DisplayName("getPublicKeyGenerator caches its result")
67 void test5() {
68 assertEquals(
69 cryptoBase.getPublicKeyGenerator(PUBLIC_KEY_LENGTH),
70 cryptoBase.getPublicKeyGenerator(PUBLIC_KEY_LENGTH));
71 }
72
73 @Test
74 @DisplayName("getPublicKeyGenerator throws CryptoError for bad keylength")
75 void test7() {
76 assertThrows(CryptoError.class, () -> cryptoBase.getPublicKeyGenerator(-1));
77 }
78
79 @Test
80 @DisplayName("doCrypto throws CryptoError if anything goes wrong")
81 void test6() {
82 SecretKeySpec skeySpec = new SecretKeySpec(SOMESTRING.getBytes(), SHARED_KEY_ALG);
83 assertThrows(
84 CryptoError.class,
85 () -> cryptoBase.doCrypto(PUBLIC_KEY_ALG, PUBLIC_KEY_PROVIDER, skeySpec, Cipher.ENCRYPT_MODE, BYTES));
86 }
87
88 @Test
89 @DisplayName("we can generate a public key pair, store them as byte array, "
90 + "recover them from the byte arrays, encrypt some bytes and "
91 + "decrypt the ciphertext correctly")
92 void testPublic() throws InvalidKeySpecException {
93 KeyPair keypair = cryptoBase.getPublicKeyGenerator(PUBLIC_KEY_LENGTH).generateKeyPair();
94 PublicKey pubKey = keypair.getPublic();
95 PrivateKey privKey = keypair.getPrivate();
96 byte[] pubkeyBytes = pubKey.getEncoded();
97 byte[] privkeyBytes = privKey.getEncoded();
98 PublicKey pubKey2 = cryptoBase.publicKeyFactory.generatePublic(new X509EncodedKeySpec(pubkeyBytes));
99 PrivateKey privKey2 = cryptoBase.publicKeyFactory.generatePrivate(new PKCS8EncodedKeySpec(privkeyBytes));
100
101 byte[] encrypted =
102 cryptoBase.doCrypto(PUBLIC_KEY_ALG, PUBLIC_KEY_PROVIDER, pubKey2, Cipher.ENCRYPT_MODE, BYTES);
103
104 byte[] decrypted =
105 cryptoBase.doCrypto(PUBLIC_KEY_ALG, PUBLIC_KEY_PROVIDER, privKey2, Cipher.DECRYPT_MODE, encrypted);
106
107 assertArrayEquals(BYTES, decrypted);
108 }
109
110 @Test
111 @DisplayName("we can obtain a probable prime with a given bit length,"
112 + "and it probably is indeed a prime with the given bit length")
113 void test1() {
114 IntStream.range(0, RANDOM_RUNS).forEach(n -> {
115 CivitasBigInteger prime = cryptoBase.obtainProbablePrime(BITLENGTH);
116 assertEquals(BITLENGTH, prime.bitLength());
117 assertTrue(prime.isProbablePrime(CERTAINTY));
118 });
119 }
120
121 @Test
122 @DisplayName("we can generate a random element for Q which is less than Q and " + "their bit lengths are similar")
123 void testGenerate() {
124 IntStream.range(0, RANDOM_RUNS).forEach(n -> {
125 CivitasBigInteger element = cryptoBase.generateRandomElement(BIGINT_A);
126 assertTrue(element.compareTo(BIGINT_A) <= 0);
127 assertTrue(BIGINT_A.bitLength() >= element.bitLength());
128 });
129 }
130
131 @Test
132 @DisplayName("we can get a random generator which is up to date and secure")
133 void test2() {
134 Random generator = cryptoBase.getRandomGenerator();
135 assertFalse(generator.isDeprecated());
136 assertEquals(SecureRandom.class, generator.getClass());
137 }
138
139 @Test
140 @DisplayName("we can get byte arrays of random numbers" + "(tested with the monoBit test from NIST 800-22 at 1%)")
141 void test3() {
142 byte[] bytes = new byte[SHARED_KEY_LENGTH];
143 cryptoBase.nextBytes(bytes);
144 monoBitTest(bytes);
145 }
146
147 public void monoBitTest(final byte[] bytes) {
148 int sum = 0;
149 for (byte element : bytes) {
150 int current = element;
151 for (int j = 0; j < 8; j++) {
152 int isOne = current & 1;
153 current = current >>> 1;
154 if (1 == isOne) {
155 sum++;
156 } else {
157 sum--;
158 }
159 }
160 }
161 double s = Math.abs(sum) / Math.sqrt(bytes.length * 8d) / Math.sqrt(2);
162 assertTrue(s < 1.82, "S=" + s);
163 }
164 }