View Javadoc
1   package net.sumaris.core.service.crypto;
2   
3   /*
4    * #%L
5    * Duniter4j :: Core API
6    * %%
7    * Copyright (C) 2014 - 2015 EIS
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the 
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public 
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
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   * Crypto services (sign...)
46   * Created by eis on 10/01/15.
47   */
48  @Component("cryptoService")
49  public class CryptoServiceImpl implements CryptoService {
50  
51      // Length of the seed key (generated deterministically, use to generate the 64 key pair).
52      private static int SEED_BYTES = 32;
53      // Length of a signature return by crypto_sign
54      private static int SIGNATURE_BYTES = 64;
55      // Length of a public key
56      private static int PUBLICKEY_BYTES = 32;
57      // Length of a secret key
58      private static int SECRETKEY_BYTES = 64;
59  
60      // Scrypt default parameters
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      // Hash
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     /* -- Internal methods -- */
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 }