1 package net.sumaris.core.service.crypto;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.lambdaworks.crypto.SCrypt;
27 import jnr.ffi.byref.LongLongByReference;
28 import net.sumaris.core.exception.SumarisTechnicalException;
29 import net.sumaris.core.util.crypto.CryptoUtils;
30 import net.sumaris.core.util.crypto.KeyPair;
31 import net.sumaris.shared.exception.ErrorCodes;
32 import org.abstractj.kalium.NaCl;
33 import org.abstractj.kalium.NaCl.Sodium;
34 import org.abstractj.kalium.crypto.Util;
35 import org.springframework.stereotype.Component;
36
37 import java.security.GeneralSecurityException;
38
39 import static org.abstractj.kalium.NaCl.Sodium.*;
40 import static org.abstractj.kalium.NaCl.sodium;
41 import static org.abstractj.kalium.crypto.Util.*;
42
43
44
45
46
47
48 @Component("cryptoService")
49 public class CryptoServiceImpl implements CryptoService {
50
51
52 private static int SEED_BYTES = 32;
53
54 private static int SIGNATURE_BYTES = 64;
55
56 private static int PUBLICKEY_BYTES = 32;
57
58 private static int SECRETKEY_BYTES = 64;
59
60
61 public static int SCRYPT_PARAMS_N = 4096;
62 public static int SCRYPT_PARAMS_r = 16;
63 public static int SCRYPT_PARAMS_p = 1;
64
65 protected final static char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
66
67
68 private static int HASH_BYTES = 256;
69
70 private final Sodium naCl;
71
72 public CryptoServiceImpl() {
73 naCl = NaCl.sodium();
74 }
75
76 @Override
77 public byte[] getSeed(String salt, String password) {
78 return getSeed(salt, password, SCRYPT_PARAMS_N, SCRYPT_PARAMS_r, SCRYPT_PARAMS_p);
79 }
80
81 @Override
82 public byte[] getSeed(String salt, String password, int N, int r, int p) {
83 try {
84 byte[] seed = SCrypt.scrypt(
85 CryptoUtils.decodeAscii(password),
86 CryptoUtils.decodeAscii(salt),
87 N, r, p, SEED_BYTES);
88 return seed;
89 } catch (GeneralSecurityException e) {
90 throw new SumarisTechnicalException(
91 "Unable to salt password, using Scrypt library", e);
92 }
93 }
94
95 @Override
96 public KeyPair getKeyPair(String salt, String password) {
97 return getKeyPairFromSeed(getSeed(salt, password));
98 }
99
100 @Override
101 public KeyPair getKeyPairFromSeed(byte[] seed) {
102 byte[] secretKey = CryptoUtils.zeros(SECRETKEY_BYTES);
103 byte[] publicKey = CryptoUtils.zeros(PUBLICKEY_BYTES);
104 Util.isValid(naCl.crypto_sign_ed25519_seed_keypair(publicKey, secretKey, seed),
105 "Failed to generate a key pair");
106
107 return new KeyPair(publicKey, secretKey);
108 }
109
110 @Override
111 public KeyPair getRandomKeypair() {
112 return getKeyPairFromSeed(String.valueOf(System.currentTimeMillis()).getBytes());
113 }
114
115 @Override
116 public String sign(String message, byte[] secretKey) {
117 byte[] messageBinary = CryptoUtils.decodeUTF8(message);
118 return CryptoUtils.encodeBase64(
119 sign(messageBinary, secretKey)
120 );
121 }
122
123 @Override
124 public String sign(String message, String secretKey) {
125 byte[] messageBinary = CryptoUtils.decodeUTF8(message);
126 byte[] secretKeyBinary = CryptoUtils.decodeBase58(secretKey);
127 return CryptoUtils.encodeBase64(
128 sign(messageBinary, secretKeyBinary)
129 );
130 }
131
132 @Override
133 public boolean verify(String message, String signature, String publicKey) {
134 byte[] messageBinary = CryptoUtils.decodeUTF8(message);
135 byte[] signatureBinary = CryptoUtils.decodeBase64(signature);
136 byte[] publicKeyBinary = CryptoUtils.decodeBase58(publicKey);
137 return verify(messageBinary, signatureBinary, publicKeyBinary);
138 }
139
140 @Override
141 public String hash(String message) {
142 byte[] hash = new byte[Sodium.CRYPTO_HASH_SHA256_BYTES];
143 byte[] messageBinary = CryptoUtils.decodeUTF8(message);
144 naCl.crypto_hash_sha256(hash, messageBinary, messageBinary.length);
145 return bytesToHex(hash).toUpperCase();
146 }
147
148 @Override
149 public byte[] getBoxRandomNonce() {
150 byte[] nonce = new byte[Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_NONCEBYTES];
151 naCl.randombytes(nonce, nonce.length);
152
153 return nonce;
154 }
155
156 @Override
157 public String box(String message, byte[] nonce, String senderSignSk, String receiverSignPk) {
158 byte[] senderSignSkBinary = CryptoUtils.decodeBase58(senderSignSk);
159 byte[] receiverSignPkBinary = CryptoUtils.decodeBase58(receiverSignPk);
160 return box(message, nonce, senderSignSkBinary, receiverSignPkBinary);
161 }
162
163 @Override
164 public String box(String message, byte[] nonce, byte[] senderSignSk, byte[] receiverSignPk) {
165 checkLength(nonce, CRYPTO_BOX_CURVE25519XSALSA20POLY1305_NONCEBYTES);
166
167 byte[] messageBinary = prependZeros(CRYPTO_BOX_CURVE25519XSALSA20POLY1305_ZEROBYTES, CryptoUtils.decodeBase64(message));
168
169 byte[] senderBoxSk = new byte[Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_SECRETKEYBYTES];
170 naCl.crypto_sign_ed25519_sk_to_curve25519(senderBoxSk, senderSignSk);
171
172 byte[] receiverBoxPk = new byte[Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_PUBLICKEYBYTES];
173 naCl.crypto_sign_ed25519_pk_to_curve25519(receiverBoxPk, receiverSignPk);
174
175 byte[] cypherTextBinary = new byte[messageBinary.length];
176 isValid(sodium().crypto_box_curve25519xsalsa20poly1305(cypherTextBinary, messageBinary,
177 cypherTextBinary.length, nonce, senderBoxSk, receiverBoxPk), "Encryption failed");
178 return CryptoUtils.encodeBase64(removeZeros(CRYPTO_BOX_CURVE25519XSALSA20POLY1305_BOXZEROBYTES, cypherTextBinary));
179 }
180
181 @Override
182 public String openBox(String cypherText, String nonce, String senderSignPk, String receiverSignSk) {
183 return openBox(cypherText,
184 CryptoUtils.decodeBase58(nonce),
185 CryptoUtils.decodeBase58(senderSignPk),
186 CryptoUtils.decodeBase58(receiverSignSk));
187 }
188
189 @Override
190 public String openBox(String cypherText, byte[] nonce, byte[] senderSignPk, byte[] receiverSignSk) {
191 checkLength(nonce, CRYPTO_BOX_CURVE25519XSALSA20POLY1305_NONCEBYTES);
192 byte[] cypherTextBinary = prependZeros(CRYPTO_BOX_CURVE25519XSALSA20POLY1305_BOXZEROBYTES, CryptoUtils.decodeBase64(cypherText));
193
194 byte[] receiverBoxSk = new byte[Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_SECRETKEYBYTES];
195 naCl.crypto_sign_ed25519_sk_to_curve25519(receiverBoxSk, receiverSignSk);
196
197 byte[] senderBoxPk = new byte[Sodium.CRYPTO_BOX_CURVE25519XSALSA20POLY1305_PUBLICKEYBYTES];
198 naCl.crypto_sign_ed25519_pk_to_curve25519(senderBoxPk, senderSignPk);
199
200 byte[] messageBinary = new byte[cypherTextBinary.length];
201 isValid(sodium().crypto_box_curve25519xsalsa20poly1305_open(
202 messageBinary, cypherTextBinary, cypherTextBinary.length, nonce, senderBoxPk, receiverBoxSk),
203 "Decryption failed. Ciphertext failed verification.");
204 return CryptoUtils.encodeUTF8(removeZeros(CRYPTO_BOX_CURVE25519XSALSA20POLY1305_ZEROBYTES, messageBinary));
205 }
206
207
208
209 protected byte[] sign(byte[] message, byte[] secretKey) {
210 byte[] signature = Util.prependZeros(SIGNATURE_BYTES, message);
211 LongLongByReference smLen = new LongLongByReference(0);
212 naCl.crypto_sign_ed25519(signature, smLen, message, message.length, secretKey);
213 signature = Util.slice(signature, 0, SIGNATURE_BYTES);
214
215 Util.checkLength(signature, SIGNATURE_BYTES);
216 return signature;
217 }
218
219 protected boolean verify(byte[] message, byte[] signature, byte[] publicKey) {
220 byte[] sigAndMsg = new byte[SIGNATURE_BYTES + message.length];
221 for (int i = 0; i < SIGNATURE_BYTES; i++) sigAndMsg[i] = signature[i];
222 for (int i = 0; i < message.length; i++) sigAndMsg[i+SIGNATURE_BYTES] = message[i];
223
224 byte[] buffer = new byte[SIGNATURE_BYTES + message.length];
225 LongLongByReference bufferLength = new LongLongByReference(0);
226
227 int result = naCl.crypto_sign_ed25519_open(buffer, bufferLength, sigAndMsg, sigAndMsg.length, publicKey);
228 boolean validSignature = (result == 0);
229
230 return validSignature;
231 }
232
233 protected static String bytesToHex(byte[] bytes) {
234 char[] hexChars = new char[bytes.length * 2];
235 for ( int j = 0; j < bytes.length; j++ ) {
236 int v = bytes[j] & 0xFF;
237 hexChars[j * 2] = HEX_CHARS[v >>> 4];
238 hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F];
239 }
240 return new String(hexChars);
241 }
242
243 }