/usr/local/lib/libsodium.so
, in Windows,
+ * it might be c:/libs/libsodium.dll
, in MacOS, it might be
+ * /usr/local/lib/libsodium.dylib
etc. The point is there is no
+ * ambiguity
+ *
+ * @author muquit@muquit.com - Oct 17, 2016, 12:16:50 PM - first cut
+ */
+ public static void setLibraryPath(String libraryPath)
+ {
+ SodiumLibrary.libPath = libraryPath;
+ }
+
+ /**
+ * @return path of library set by SodiumLibary.setLibraryPath()
+ * @author muquit@muquit.com - Oct 21, 2016, 3:03:59 PM - first cut
+ */
+ public static String getLibaryPath()
+ {
+ return SodiumLibrary.libPath;
+ }
+
+ /**
+ * @return The singleton Sodium object. The single pattern is adapted from
+ * kalium java library. Other than that, this library does not use any code
+ * from kalium.
+ *
+ * Although libsodium seems to the thread safe now, the code is written
+ * sometime back and I don't have plan to remove it at this time.
+ *
+ * @author muquit@muquit.com
+ *
+ * @throws RuntimeException at run time if the libsodium library path
+ * is not set by calling SodidumLibary.setLibraryPath(path)
.
+ */
+ public static Sodium sodium()
+ {
+ if (SodiumLibrary.libPath == null)
+ {
+ logger.info("libpath not set, throw exception");
+ throw new RuntimeException("Please set the absolute path of the libsodium libary by calling SodiumLibrary.setLibraryPath(path)");
+ }
+ Sodium sodium = SingletonHelper.instance;
+ String h = Integer.toHexString(System.identityHashCode(sodium));
+ int rc = sodium.sodium_init();
+ if (rc == -1)
+ {
+ logger.error("ERROR: sodium_init() failed: " + rc);
+ throw new RuntimeException("sodium_init() failed, rc=" + rc);
+ }
+ return sodium;
+ }
+
+ private static final class SingletonHelper
+ {
+ public static final Sodium instance = (Sodium) Native.loadLibrary(libPath,Sodium.class);
+ }
+
+ /**
+ * Declare all the supported libsodium functions in this interface and
+ * implement them in this class as static methods.
+ *
+ * @author muquit@muquit.com - Oct 21, 2016, 11:44:45 AM - first cut
+ */
+ public interface Sodium extends Library
+ {
+ int sodium_library_version_major();
+ int sodium_library_version_minor();
+ int sodium_init();
+
+ /**
+ * @return version string of the libsodium library
+ * @see include/sodium/version.h
+ */
+ String sodium_version_string();
+
+ /**
+ * Fills size bytes starting at buf with an unpredictable sequence of bytes.
+ * @param buf buffer to fill with random bytes
+ * @param size number of random bytes
+ * @see Generating random data libsodium page
+ */
+ void randombytes_buf(byte[] buf, int size);
+
+ /**
+ *
+ * @see include/sodium/crypto_pwhash.h
+ */
+ int crypto_pwhash_alg_argon2i13();
+ int crypto_pwhash_alg_default();
+ int crypto_pwhash_saltbytes();
+ int crypto_pwhash_strbytes();
+ Pointer crypto_pwhash_strprefix();
+ long crypto_pwhash_opslimit_interactive();
+ NativeLong crypto_pwhash_memlimit_interactive();
+ long crypto_pwhash_opslimit_moderate();
+ NativeLong crypto_pwhash_memlimit_moderate();
+ long crypto_pwhash_opslimit_sensitive();
+ NativeLong crypto_pwhash_memlimit_sensitive();
+
+ /* sodium/crypto_box.h */
+ long crypto_box_seedbytes();
+ long crypto_box_publickeybytes();
+ long crypto_box_secretkeybytes();
+ long crypto_box_noncebytes();
+ long crypto_box_macbytes();
+ long crypto_box_sealbytes();
+
+ /* sodium/crypto_auth.h */
+ long crypto_auth_bytes();
+ long crypto_auth_keybytes();
+
+ /**
+ *
+ * @param key
+ * @param keylen
+ * @param passwd
+ * @param passwd_len
+ * @param in_salt
+ * @param opslimit
+ * @param memlimit
+ * @param alg
+ * @return 0 on success -1 on error
+ * @see Password hashing libsodium page
+ */
+ int crypto_pwhash(byte[] key, long keylen,
+ byte[] passwd, long passwd_len,
+ byte[] in_salt,
+ long opslimit,
+ NativeLong memlimit,
+ int alg);
+
+ /**
+ * Derives a key from a password using Argon2
+ * @param key
+ * @param keylen
+ * @param passwd
+ * @param passwd_len
+ * @param in_salt
+ * @param opslimit
+ * @param memlimit
+ * @return 0 on success -1 on error
+ */
+ int crypto_pwhash_scryptsalsa208sha256(byte[] key, long keyLength,
+ byte[] passwd, long passwd_len,
+ byte[] in_salt,
+ long opslimit,
+ NativeLong memlimit);
+
+ int crypto_pwhash_str(byte[] hashedPassword,
+ byte[] password, long passwordLen,
+ long opslimit, NativeLong memlimit);
+
+ int crypto_pwhash_str_verify(byte[] hashedPassword,
+ byte[] password, long passwordLen);
+
+ /* sodium/crypto_pwhash_scryptsalsa208sha256.h */
+ long crypto_pwhash_scryptsalsa208sha256_saltbytes();
+
+ /* Secret Key */
+ long crypto_secretbox_keybytes();
+ long crypto_secretbox_noncebytes();
+ long crypto_secretbox_macbytes();
+
+ int crypto_secretbox_easy(byte[] cipherText,
+ byte[] message, long mlen, byte[] nonce,
+ byte[] key);
+
+ int crypto_secretbox_open_easy(byte[] decrypted,
+ byte[] cipherText, long ct_len, byte[] nonce,
+ byte[] key);
+
+ int crypto_secretbox_detached(byte[] cipherText,
+ byte[] mac,
+ byte[] message, long mlen,
+ byte[] nonce, byte[] key);
+
+ int crypto_secretbox_open_detached(byte[] message,
+ byte[] cipherText, byte[] mac, long cipherTextLength,
+ byte[] nonce, byte[] key);
+
+ int crypto_box_seal(byte[] cipherText,
+ byte[] message, long messageLen,
+ byte[] recipientPublicKey);
+
+ int crypto_box_seal_open(byte[] decrypted,
+ byte[] cipherText, long cipherTextLen,
+ byte[] recipientPublicKey, byte[] reciPientPrivateKey);
+
+ int crypto_auth(byte[] mac, byte[] message, long messageLen, byte[] key);
+ int crypto_auth_verify(byte[] mac, byte[] message, long messagelen, byte[] key);
+
+
+ /* Public key authenticated encryption */
+ int crypto_box_keypair(byte[] pk, byte[] sk);
+
+ /**
+ * Compute Public key from Private Key
+ * @param pk - Public Key returns
+ * @param sk - Private Key
+ * @return 0 on success -1 on failure
+ * @author muquit@muquit.com - Oct 18, 2016, 4:39:20 PM - first cut
+ */
+ int crypto_scalarmult_base(byte[] pk, byte[] sk);
+
+ int crypto_box_easy(byte[] cipher_text,
+ byte[] plain_text, long pt_len,
+ byte[] nonce,
+ byte[] public_key, byte[] private_key);
+
+ int crypto_box_open_easy(byte[] decrypted, byte[] cipher_text,
+ long ct_len, byte[] nonce,
+ byte[] public_key, byte[] private_key);
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @return version string of libsodium
+ */
+ public static String libsodiumVersionString()
+ {
+ return sodium().sodium_version_string();
+ }
+
+
+ /**
+ * Return unpredictable sequence of bytes.
+ *
+ * Excerpt from libsodium documentation: + *
+ * + *+ *+ *
+ * + *- On Windows systems, the RtlGenRandom() function is used + *
- On OpenBSD and Bitrig, the arc4random() function is used + *
- On recent Linux kernels, the getrandom system call is used (since Sodium 1.0.3) + *
- On other Unices, the /dev/urandom device is used + *
- If none of these options can safely be used, custom implementations can easily be hooked. + *
+ * @param size Number of random bytes to generate + *
+ * @return Array of random bytes + *
+ * @author muquit@muquit.com - Oct 21, 2016 + *
+ * @see randombytes_buf()
in Generating random data libsodium page
+ */
+ public static byte[] randomBytes(int size)
+ {
+ byte[] buf = new byte[size];
+ sodium().randombytes_buf(buf, size);
+ return buf;
+ }
+ /*
+ int crypto_pwhash(byte[] key, long keylen,
+ byte[] passwd, long passwd_len,
+ byte[] in_salt,
+ long opslimit,
+ NativeLong memlimit,
+ int alg);
+ */
+
+ public static byte[] cryptoPwhash(byte[] passwd, byte[] salt, long opsLimit, NativeLong memLimit, int algorithm) throws SodiumLibraryException
+ {
+ byte[] key = new byte[(int) sodium().crypto_box_seedbytes()];
+ logger.info(">>> NavtiveLong size: " + NativeLong.SIZE * 8 + " bits");
+
+ int rc = sodium().crypto_pwhash(key, key.length,
+ passwd, passwd.length,
+ salt,
+ opsLimit,
+ memLimit,
+ algorithm);
+
+ logger.info("crypto_pwhash returned: " + rc);
+ if (rc != 0)
+ {
+ throw new SodiumLibraryException("cryptoPwhash libsodium crypto_pwhash failed, returned " + rc + ", expected 0");
+ }
+ return key;
+
+ }
+
+ /**
+ * Derive a key using Argon2i password hashing scheme
+ *
+ * The following is taken from libsodium documentation: + *
+ * + * Argon2 is optimized for the x86 architecture and exploits the cache and memory organization of the recent Intel + *+ * + * @param passwd Array of bytes of password + *
+ * and AMD processors. But its implementation remains portable and fast on other architectures. + *+ * Argon2 has two variants: Argon2d and Argon2i. Argon2i uses data-independent memory access, which is preferred + *
+ * for password hashing and password-based key derivation. Argon2i also makes multiple passes over the memory to + *
+ * protect from tradeoff attacks. + *+ * This is the variant implemented in Sodium since version 1.0.9. + *
+ * Argon2 is recommended over Scrypt if requiring libsodium >= 1.0.9 is not a concern. + *
+ *
+ * @param salt Salt to use in key generation. The salt should be + * unpredictable and can be generated by calling {@link #SodiumLibary.randomBytes(int)} + *
+ * The salt should be saved by the caller as it will be needed to derive the key again from the + * password. + *
+ * @return Generated key as an array of bytes + *
+ * @throws SodiumLibraryException
if libsodium's
+ * crypto_pwhash()
does not return 0
+ *
+ * @see crypto_pwhash()
in Password hashing
+ * libsodium page. Argon2i v1.3 Algorithm.
+ *
+ * Example: + *
+ * {@code + * + String password = "This is a Secret"; + salt = SodiumLibary.randomBytes(sodium().crypto_pwhash_saltbytes()); + byte[] key = SodiumLibary.cryptoPwhashArgon2i(password.getBytes(),salt); + String keyHex = SodiumUtils.bin2hex(key); + *} + *+ */ + public static byte[] cryptoPwhashArgon2i(byte[] passwd, byte[] salt) throws SodiumLibraryException + { + int saltLength = cryptoPwhashSaltBytes(); + if (salt.length != saltLength) + { + throw new SodiumLibraryException("salt is " + salt.length + ", it must be" + saltLength + " bytes"); + } + + byte[] key = new byte[(int) sodium().crypto_box_seedbytes()]; + logger.info(">>> NavtiveLong size: " + NativeLong.SIZE * 8 + " bits"); + logger.info(">>> opslimit: " + sodium().crypto_pwhash_opslimit_interactive()); + logger.info(">>> memlimit: " + sodium().crypto_pwhash_memlimit_interactive()); + logger.info(">>> alg: " + sodium().crypto_pwhash_alg_argon2i13()); + + int rc = sodium().crypto_pwhash(key, key.length, + passwd, passwd.length, + salt, + sodium().crypto_pwhash_opslimit_interactive(), + sodium().crypto_pwhash_memlimit_interactive(), + sodium().crypto_pwhash_alg_argon2i13()); + + logger.info("crypto_pwhash returned: " + rc); + if (rc != 0) + { + throw new SodiumLibraryException("cryptoPwhashArgon2i libsodium crypto_pwhash failed, returned " + rc + ", expected 0"); + } + return key; + } + + /** + * A helper function to call cryptoPwhashArgon2i() + * + * @param passwd Password bytes + * @param salt Salt bytes + * @return + * @throws SodiumLibraryException + *
+ * @author muquit@muquit.com - Mar 18, 2017 + */ + public static byte[] deriveKey(byte[] passwd, byte[] salt) throws SodiumLibraryException + { + return cryptoPwhashArgon2i(passwd, salt); + } + + + /** + * Return US-ASCII encoded key derives from the password + * + * @param password password to use in key derivation + * @return US-ASCII encoded string + *
+ * @author muquit@muquit.com - Oct 22, 2016 + */ + public static String cryptoPwhashStr(byte[] password) throws SodiumLibraryException + { + byte[] hashedPassword = new byte[sodium().crypto_pwhash_strbytes()]; + int rc = sodium().crypto_pwhash_str(hashedPassword, + password, password.length, + sodium().crypto_pwhash_opslimit_interactive(), + sodium().crypto_pwhash_memlimit_interactive()); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_pwhash_str failed, returned " + rc + ", expected 0"); + } + String usAscii = new String(hashedPassword,StandardCharsets.US_ASCII); + + return usAscii; + } + + /** + * Verify a US-ASCII encoded key derived previously by calling {@link #SodiumLibrary.cryptoPwhashStr(byte[])} + * + * @param usAsciiKey + * @param password + * @return true if the key can be verified false otherwise + *
+ * @author muquit@muquit.com - Oct 22, 2016 + */ + public static boolean cryptoPwhashStrVerify(String usAsciiKey, byte[] password) + { + byte[] keyBytes = usAsciiKey.getBytes(StandardCharsets.US_ASCII); + int rc = sodium().crypto_pwhash_str_verify(keyBytes, password, password.length); + if (rc == 0) + { + return true; + } + return false; + } + + + /** + * Derive key from a password using scrypt + *
+ * Excerpt from libsodium documentation: + *
+ * + * Scrypt was also designed to make it costly to perform large-scale custom hardware attacks by requiring large + * amounts of memory. + *+ * Even though its memory hardness can be significantly reduced at the cost of extra computations, this function + * remains an excellent choice today, provided that its parameters are properly chosen. + *
+ * Scrypt is available in libsodium since version 0.5.0, which makes it a better choice than Argon2 if compatibility with older libsodium versions is a concern. + *
+ * + * + * @param passwd Array of bytes of password + *+ * @param salt Salt to use in key generation. The salt should be + * unpredictable and can be generated by calling {@link #SodiumLibary.randomBytes(int)} + *
+ * The salt should be saved by the caller as it will be needed to derive the key again from the + * password. + *
+ * @return key as an array of bytes + * + * @see
crypto_pwhash()
in Password hashing + * libsodium page. + */ + public static byte[] cryptoPwhashScrypt(byte[] passwd, byte[] salt) throws SodiumLibraryException + { + long salt_length = sodium().crypto_pwhash_scryptsalsa208sha256_saltbytes(); + if (salt.length != salt_length) + { + throw new SodiumLibraryException("salt is " + salt.length + ", it must be" + salt_length + " bytes"); + } + byte[] key = new byte[(int) sodium().crypto_box_seedbytes()]; + int rc = sodium().crypto_pwhash_scryptsalsa208sha256(key, key.length, + passwd, passwd.length, + salt, + sodium().crypto_pwhash_opslimit_interactive(), + sodium().crypto_pwhash_memlimit_interactive()); + + logger.info("crypto_pwhash_scryptsalsa208sha256 returned: " + rc); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_pwhash_scryptsalsa208sha256() failed, returned " + rc + ", expected 0"); + } + return key; + } + + public static byte[] cryptoPwhashScryptSalsa208Sha256(byte[] passwd, byte[] salt, + Long opsLimit, + NativeLong memLimit) throws SodiumLibraryException + { + long salt_length = sodium().crypto_pwhash_scryptsalsa208sha256_saltbytes(); + if (salt.length != salt_length) + { + throw new SodiumLibraryException("salt is " + salt.length + ", it must be" + salt_length + " bytes"); + } + byte[] key = new byte[(int) sodium().crypto_box_seedbytes()]; + int rc = sodium().crypto_pwhash_scryptsalsa208sha256(key, key.length, + passwd, passwd.length, + salt, + opsLimit, memLimit); + + logger.info("crypto_pwhash_scryptsalsa208sha256 returned: " + rc); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_pwhash_scryptsalsa208sha256() failed, returned " + rc + ", expected 0"); + } + return key; + + } + + public static byte[] cryptoSecretBoxEasy(byte[] message, byte[] nonce, byte[] key) throws SodiumLibraryException + { + long nonce_length = sodium().crypto_secretbox_noncebytes(); + if (nonce_length != nonce.length) + { + throw new SodiumLibraryException("nonce is " + nonce.length + ", it must be" + nonce_length + " bytes"); + } + byte[] cipherText = new byte[(int) (sodium().crypto_box_macbytes() + message.length)]; + + int rc = sodium().crypto_secretbox_easy(cipherText,message,message.length,nonce,key); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_secretbox_easy() failed, returned " + rc + ", expected 0"); + } + return cipherText; + } + + public static byte[] cryptoSecretBoxOpenEasy(byte[] cipherText,byte[] nonce, byte[] key) throws SodiumLibraryException + { + if (key.length != sodium().crypto_secretbox_keybytes()) + { + throw new SodiumLibraryException("invalid key length " + key.length + " bytes"); + } + + if (nonce.length != sodium().crypto_secretbox_noncebytes()) + { + throw new SodiumLibraryException("invalid nonce length " + nonce.length + " bytes"); + } + + byte[] decrypted = new byte[(int) (cipherText.length - sodium().crypto_box_macbytes())]; + int rc = sodium().crypto_secretbox_open_easy(decrypted,cipherText,cipherText.length,nonce,key); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_secretbox_open_easy() failed, returned " + rc + ", expected 0"); + } + return decrypted; + } + /* + int crypto_secretbox_detached(byte[] cipherText, + byte[] mac, + byte[] message, long mlen, + byte[] nonce, byte[] key); + + */ + public static SodiumSecretBox cryptoSecretBoxDetached(byte[] message, byte[] nonce, byte[] key) throws SodiumLibraryException + { + if (key.length != sodium().crypto_secretbox_keybytes()) + { + throw new SodiumLibraryException("invalid key length " + key.length + " bytes"); + } + + + if (nonce.length != sodium().crypto_secretbox_noncebytes()) + { + throw new SodiumLibraryException("invalid nonce length " + nonce.length + " bytes"); + } + byte[] cipherText = new byte[message.length]; + byte[] mac = new byte[(int) sodium().crypto_secretbox_macbytes()]; + + int rc = sodium().crypto_secretbox_detached(cipherText,mac,message,message.length,nonce,key); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_secretbox_detached() failed, returned " + rc + ", expected 0"); + } + SodiumSecretBox secretBox = new SodiumSecretBox(); + secretBox.setCipherText(cipherText); + secretBox.setMac(mac); + + return secretBox; + } + /* + int crypto_secretbox_open_detached(byte[] message, + byte[] cipherText, byte[] mac, long cipherTextLength, + byte[] nonce, byte[] key); + */ + + public static byte[] cryptoSecretBoxOpenDetached(SodiumSecretBox secretBox, + byte[] nonce, byte[] key) throws SodiumLibraryException + { + if (key.length != sodium().crypto_secretbox_keybytes()) + { + throw new SodiumLibraryException("invalid key length " + key.length + " bytes"); + } + + if (nonce.length != sodium().crypto_secretbox_noncebytes()) + { + throw new SodiumLibraryException("invalid nonce length " + nonce.length + " bytes"); + } + byte[] mac = secretBox.getMac(); + if (mac.length != sodium().crypto_secretbox_macbytes()) + { + throw new SodiumLibraryException("invalid mac length " + mac.length + " bytes"); + } + + byte[] message = new byte[secretBox.getCipherText().length]; + byte[] cipherText = secretBox.getCipherText(); + + int rc = sodium().crypto_secretbox_open_detached(message,cipherText,mac,cipherText.length,nonce,key); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_secretbox_open_detached() failed, returned " + rc + ", expected 0"); + } + return message; + } + + /** + * Key is confidential but mac and message can be public + * @param message + * @param key + * @return mac + * @throws SodiumLibraryException + */ + public static byte[] cryptoAuth(byte[] message, byte[] key) throws SodiumLibraryException + { + byte[] mac = new byte[(int) sodium().crypto_auth_bytes()]; + + long keySize = sodium().crypto_auth_keybytes(); + if (key.length != keySize) + { + throw new SodiumLibraryException("Expected key size " + keySize + " bytes, but passed " + key.length + " bytes"); + } + int rc = sodium().crypto_auth(mac, message, message.length, key); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_auth() failed, returned " + rc + ", expected 0"); + } + return mac; + } + + /** + * Verify the public mac of the message is correct + * @param mac + * @param message + * @param key + * @return true if mac is correct + * @throws SodiumLibraryException + */ + public static boolean cryptoAuthVerify(byte[] mac, byte[] message, byte[] key) throws SodiumLibraryException + { + long keySize = sodium().crypto_auth_keybytes(); + if (key.length != keySize) + { + throw new SodiumLibraryException("Expected key size " + keySize + " bytes, but passed " + key.length + " bytes"); + } + int rc = sodium().crypto_auth_verify(mac, message, message.length, key); + if (rc == 0) + { + return true; + } + else if(rc == -1) + { + return false; + } + + return false; + } + + + /** + * Randomly generates a ecret key and a corresponding public key + * + * @return SodiumKeyPair + *+ * @author muquit@muquit.com - Nov 20, 2016 + * @throws SodiumLibraryException + */ + public static SodiumKeyPair cryptoBoxKeyPair() throws SodiumLibraryException + { + SodiumKeyPair kp = new SodiumKeyPair(); + byte[] publicKey = new byte[(int) sodium().crypto_box_publickeybytes()]; + byte[] privateKey = new byte[(int) sodium().crypto_box_secretkeybytes()]; + int rc = sodium().crypto_box_keypair(publicKey, privateKey); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_box_keypair() failed, returned " + rc + ", expected 0"); + } + kp.setPublicKey(publicKey); + kp.setPrivateKey(privateKey); + logger.info("pk len: " + publicKey.length); + logger.info("sk len: " + privateKey.length); + return kp; + } + + public static byte[] cryptoPublicKey(byte[] privateKey) throws SodiumLibraryException + { + byte[] publicKey = new byte[(int) sodium().crypto_box_publickeybytes()]; + int rc = sodium().crypto_scalarmult_base(publicKey,privateKey); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_scalrmult() failed, returned " + rc + ", expected 0"); + } + return publicKey; + } + + public static long cryptoBoxNonceBytes() + { + return sodium().crypto_box_noncebytes(); + } + + public static long crytoBoxSeedBytes() + { + return sodium().crypto_box_seedbytes(); + } + + public static long crytoBoxPublicKeyBytes() + { + return sodium().crypto_box_publickeybytes(); + } + + public static long crytoBoxSecretKeyBytes() + { + return sodium().crypto_box_secretkeybytes(); + } + + public static long cryptoBoxMacBytes() + { + return sodium().crypto_box_macbytes(); + } + + public static long cryptoBoxSealBytes() + { + return sodium().crypto_box_sealbytes(); + } + + /* secret-key */ + public static long cryptoSecretBoxKeyBytes() + { + return sodium().crypto_secretbox_keybytes(); + } + + public static long cryptoSecretBoxNonceBytes() + { + return sodium().crypto_secretbox_noncebytes(); + } + + public static long cryptoSecretBoxMacBytes() + { + return sodium().crypto_secretbox_macbytes(); + } + + /** + * @return number of salt bytes + *
+ * @author muquit@muquit.com - Jan 1, 2017 + */ + public static int cryptoNumberSaltBytes() + { + return sodium().crypto_pwhash_saltbytes(); + } + + public static int cryptoPwhashAlgArgon2i13() + { + return sodium().crypto_pwhash_alg_argon2i13(); + } + + public static int cryptoPwhashAlgDefault() + { + return sodium().crypto_pwhash_alg_default(); + } + + public static int cryptoPwhashSaltBytes() + { + return sodium().crypto_pwhash_saltbytes(); + } + + public static long cryptoPwHashOpsLimitInteractive() + { + return sodium().crypto_pwhash_opslimit_interactive(); + } + + public static NativeLong cryptoPwHashMemLimitInterative() + { + return sodium().crypto_pwhash_memlimit_interactive(); + } + + public static long cryptoPwHashScryptSalsa208Sha256SaltBytes() + { + return sodium().crypto_pwhash_scryptsalsa208sha256_saltbytes(); + } + + + public static byte[] cryptoBoxEasy(byte[] message, byte[] nonce, + byte[] publicKey, byte[] privateKey) throws SodiumLibraryException + { + long nonce_len = sodium().crypto_box_noncebytes(); + if (nonce.length != nonce_len) + { + throw new SodiumLibraryException("nonce is " + nonce.length + "bytes, it must be" + nonce_len + " bytes"); + } + byte[] cipherText = new byte[(int) (sodium().crypto_box_macbytes() + message.length)]; + int rc = sodium().crypto_box_easy(cipherText, + message,message.length, + nonce, + publicKey, privateKey); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_box_easy() failed, returned " + rc + ", expected 0"); + } + + return cipherText; + } + + public static byte[] cryptoBoxOpenEasy(byte[] cipherText, byte[]nonce, + byte[] publicKey, byte[] privateKey) throws SodiumLibraryException + { + long nonce_len = sodium().crypto_box_noncebytes(); + if (nonce.length != nonce_len) + { + throw new SodiumLibraryException("nonce is " + nonce.length + "bytes, it must be" + nonce_len + " bytes"); + } + byte[] decrypted = new byte[(int) (cipherText.length - sodium().crypto_box_macbytes())]; + int rc = sodium().crypto_box_open_easy(decrypted, cipherText, + cipherText.length, nonce, + publicKey, privateKey); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_box_open_easy() failed, returned " + rc + ", expected 0"); + } + + return decrypted; + } + + public static byte[] cryptoBoxSeal(byte[] message, byte[] recipientPublicKey) throws SodiumLibraryException + { + logger.info("message len: " + message.length); +// byte[] cipherText = new byte[62]; // WTF is that?? + byte[] cipherText = new byte[(int) (sodium().crypto_box_sealbytes() + message.length)]; + int rc = sodium().crypto_box_seal(cipherText, message, message.length, recipientPublicKey); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_box_seal() failed, returned " + rc + ", expected 0"); + } + return cipherText; + } + + public static byte[] cryptoBoxSealOpen(byte[] cipherText,byte[] pk, byte[] sk) throws SodiumLibraryException + { + byte[] decrypted = new byte[(int) (cipherText.length - sodium().crypto_box_sealbytes())]; + int rc = sodium().crypto_box_seal_open(decrypted, cipherText, cipherText.length, pk, sk); + if (rc != 0) + { + throw new SodiumLibraryException("libsodium crypto_box_seal_open() failed, returned " + rc + ", expected 0"); + } + return decrypted; + + } + //////////////////// +} diff --git a/src/main/java/com/muquit/libsodiumjna/SodiumSecretBox.java b/src/main/java/com/muquit/libsodiumjna/SodiumSecretBox.java new file mode 100755 index 0000000..41c973c --- /dev/null +++ b/src/main/java/com/muquit/libsodiumjna/SodiumSecretBox.java @@ -0,0 +1,31 @@ +package com.muquit.libsodiumjna; + +/** + * Class to hold cipher text and mac for secret box + * @author muquit@muquit.com - Oct 22, 2016 + */ +public class SodiumSecretBox +{ + private byte[] cipherText; + private byte[] mac; + + public byte[] getCipherText() + { + return this.cipherText; + } + + public byte[] getMac() + { + return this.mac; + } + + public void setCipherText(byte[] cipherText) + { + this.cipherText = cipherText; + } + + public void setMac(byte[] mac) + { + this.mac = mac; + } +} diff --git a/src/main/java/com/muquit/libsodiumjna/SodiumUtils.java b/src/main/java/com/muquit/libsodiumjna/SodiumUtils.java new file mode 100755 index 0000000..8ad9420 --- /dev/null +++ b/src/main/java/com/muquit/libsodiumjna/SodiumUtils.java @@ -0,0 +1,41 @@ +package com.muquit.libsodiumjna; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A helper class to convert hex to binary and vice versa. + * I get confused with encode decode methods of Hex class and + * always have to look it up! + * @author muquit@muquit.com - Oct-09-2016 + */ + +public class SodiumUtils +{ + private final static Logger logger = LoggerFactory.getLogger(SodiumUtils.class); + + public static byte[] hex2Binary(String hexString) + { + byte[] data = null; + try + { + data = Hex.decodeHex(hexString.toCharArray()); + } catch (DecoderException e) + { + e.printStackTrace(); + logger.error("Exception caught: " + e.getLocalizedMessage()); + return null; + } + return data; + } + + public static String binary2Hex(byte[] data) + { + String hexString = Hex.encodeHexString(data); + return hexString; + } + + +} diff --git a/src/main/java/com/muquit/libsodiumjna/exceptions/SodiumLibraryException.java b/src/main/java/com/muquit/libsodiumjna/exceptions/SodiumLibraryException.java new file mode 100755 index 0000000..77ec082 --- /dev/null +++ b/src/main/java/com/muquit/libsodiumjna/exceptions/SodiumLibraryException.java @@ -0,0 +1,33 @@ +package com.muquit.libsodiumjna.exceptions; + +public class SodiumLibraryException extends Exception +{ + + private static final long serialVersionUID = 30349089390987L; + private String message; + + public SodiumLibraryException() { message = null; } + + public SodiumLibraryException(final String message) + { + super(message); + this.message = message; + } + + public SodiumLibraryException(final Throwable cause) + { + super(cause); + message = (cause != null) ? cause.getMessage() : null; + } + + public SodiumLibraryException(final String message, final Throwable cause) + { + super(message,cause); + this.message = message; + } + + public String getMessage() + { + return message; + } +} diff --git a/src/test/java/test/com/muquit/libsodiumjna/TestInitializeLibrary.java b/src/test/java/test/com/muquit/libsodiumjna/TestInitializeLibrary.java new file mode 100644 index 0000000..ed9c099 --- /dev/null +++ b/src/test/java/test/com/muquit/libsodiumjna/TestInitializeLibrary.java @@ -0,0 +1,60 @@ +package test.com.muquit.libsodiumjna; + +import org.slf4j.LoggerFactory; + +import com.muquit.libsodiumjna.SodiumLibrary; +import com.muquit.libsodiumjna.exceptions.SodiumLibraryException; +import com.sun.jna.Platform; + +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; + +public class TestInitializeLibrary +{ + private static final Logger logger = LoggerFactory.getLogger(TestInitializeLibrary.class); + static { + String libraryPath = null; + System.out.println("hi form static"); + String platform = System.getProperty("os.name"); + logger.info("Platform: " + platform); + if (Platform.isMac()) + { + libraryPath = "/usr/local/lib/libsodium.dylib"; + logger.info("Library path in Mac: " + libraryPath); + } + else if (Platform.isWindows()) + { + libraryPath = "C:/libsodium/libsodium.dll"; + logger.info("Library path in Windows: " + libraryPath); + } + else + { + // Possibly Linux + libraryPath = "/usr/local/lib/libsodium.so"; + logger.info("Library path in " + "platform: " + platform + " " + libraryPath); + + } + logger.info("Initialize libsodium..."); + SodiumLibrary.setLibraryPath(libraryPath); + + } + + public String hashPassword(String password) throws SodiumLibraryException + { + byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8); // requires jdk 1.7+ + return SodiumLibrary.cryptoPwhashStr(passwordBytes); + } + + public boolean check(String password, String hashedPassword) + { + byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8); // requires jdk 1.7+ + return SodiumLibrary.cryptoPwhashStrVerify(hashedPassword, passwordBytes); + } + + public static void main(String[] args) + { + logger.info("libsodium version: " + SodiumLibrary.libsodiumVersionString()); + } + +} diff --git a/src/test/java/test/com/muquit/libsodiumjna/TestSodiumLibrary.java b/src/test/java/test/com/muquit/libsodiumjna/TestSodiumLibrary.java new file mode 100755 index 0000000..e51be0a --- /dev/null +++ b/src/test/java/test/com/muquit/libsodiumjna/TestSodiumLibrary.java @@ -0,0 +1,428 @@ +package test.com.muquit.libsodiumjna; + +import static com.muquit.libsodiumjna.SodiumLibrary.sodium; +import static org.junit.Assert.*; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//import com.muquit.libsodiumjna.SodiumCrypto; +import com.muquit.libsodiumjna.SodiumKeyPair; +import com.muquit.libsodiumjna.SodiumLibrary; +import com.muquit.libsodiumjna.SodiumSecretBox; +import com.muquit.libsodiumjna.SodiumUtils; +import com.muquit.libsodiumjna.exceptions.SodiumLibraryException; +import com.sun.jna.Native; +import com.sun.jna.Platform; + +public class TestSodiumLibrary +{ + private final static Logger logger = LoggerFactory.getLogger(TestSodiumLibrary.class); + private static String libraryPath = null; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void initSodium() + { + String platform = System.getProperty("os.name"); + logger.info("Platform: " + platform); + if (Platform.isMac()) + { + libraryPath = "/usr/local/lib/libsodium.dylib"; + logger.info("Library path in Mac: " + libraryPath); + } + else if (Platform.isWindows()) + { + libraryPath = "C:/libsodium/libsodium.dll"; + logger.info("Library path in Windows: " + libraryPath); + } + else + { + libraryPath = "/usr/local/lib/libsodium.so"; + logger.info("Library path in " + "platform: " + platform + " " + libraryPath); + + } + logger.info("Initialize libsodium..."); + SodiumLibrary.setLibraryPath(libraryPath); + logger.info("sodium object: " + Integer.toHexString(System.identityHashCode(sodium()))); + logger.info("Library path: " + libraryPath); + String v = sodium().sodium_version_string(); + logger.info("libsodium version: " + v); + } + + /* convert binary to hex */ + public String binary2Hex(byte[] data) + { + return SodiumUtils.binary2Hex(data); + } + + public byte[] hex2Binary(String hex) + { + return SodiumUtils.hex2Binary(hex); + } + + @Test + public void testLibSodiumVersion() + { + String version = SodiumLibrary.libsodiumVersionString(); + assertEquals("1.0.12", version); + } + + @Test + public void testRandomBytes() + { + int n = SodiumLibrary.cryptoNumberSaltBytes(); + logger.info("Generate " + n + " random bytes"); + byte[] salt = SodiumLibrary.randomBytes(n); + logger.info("Generated " + salt.length + " random bytes"); + String hex = SodiumUtils.binary2Hex(salt); + logger.info("Random bytes; " + hex); + assertEquals(n * 2, hex.length()); + } + + @Test + public void testDeriveKeyArgon2() throws SodiumLibraryException + { + String hexString = TestVectors.PWHASH_ARGON2_SALT; + byte[] saltBytes = SodiumUtils.hex2Binary(hexString); + byte[] passPhraseBytes = TestVectors.PASSWORD; + byte[] key; + String keyHex = null; + try + { + key = SodiumLibrary.cryptoPwhashArgon2i(passPhraseBytes, saltBytes); + keyHex = SodiumUtils.binary2Hex(key); + logger.info("key: " + keyHex); + assertEquals(keyHex,TestVectors.PWHASH_ARGON2_KEY); + } catch (SodiumLibraryException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + throw(e); + } + + key = SodiumLibrary.cryptoPwhashArgon2i(passPhraseBytes, saltBytes); + keyHex = SodiumUtils.binary2Hex(key); + assertEquals(keyHex,TestVectors.PWHASH_ARGON2_KEY); + } + @Test + public void testDeriveKeyVerify() throws SodiumLibraryException + { + String password = new String("বাংলা"); + // convert to UTF-8 encoded bytes + byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8); // requires jdk 1.7+ + String key = SodiumLibrary.cryptoPwhashStr(passwordBytes); + boolean rc = SodiumLibrary.cryptoPwhashStrVerify(key, passwordBytes); + assertEquals(true,rc); + } + + /* + @Test + public void testPasswordStorageArgon2() + { + byte[] hashedPassword = SodiumLibrary.cryptoPwhashStr(TestVectors.PASSWORD); + String hashedPasswordStr = Native.toString(hashedPassword); + logger.info("hashed password: " + hashedPassword); + logger.info("hashed password string: " + hashedPasswordStr); + + boolean rc = SodiumLibrary.cryptoPwhashStrVerify(hashedPassword, TestVectors.PASSWORD); + assertEquals(true,rc); + + // test converting String to byes and convert back to String + byte[] p = hashedPasswordStr.getBytes(); + String b = Native.toString(p); + logger.info("back: " + b); + assertEquals(b,hashedPasswordStr); + } + */ + + @Test + public void testDeriveKeyScrypt() throws SodiumLibraryException + { + String hexString = TestVectors.PWHASH_SCRYPT_SALT; + byte[] saltBytes = SodiumUtils.hex2Binary(hexString); + byte[] passPhraseBytes = TestVectors.PASSWORD; + byte[] key = SodiumLibrary.cryptoPwhashScrypt(passPhraseBytes, saltBytes); + String keyHex = SodiumUtils.binary2Hex(key); + logger.info("key: " + keyHex); + assertEquals(keyHex,TestVectors.PWHASH_SCRYPT_KEY); + } + + @Test + public void testKeyPair() throws SodiumLibraryException + { + SodiumKeyPair kp = SodiumLibrary.cryptoBoxKeyPair(); + String hex = SodiumUtils.binary2Hex(kp.getPublicKey()); + logger.info("Public key: " + hex); + hex = SodiumUtils.binary2Hex(kp.getPrivateKey()); + logger.info("Private key: " + hex); + } + + @Test + public void testPublicKeyFromPrivateKey() throws SodiumLibraryException + { + SodiumKeyPair kp = SodiumLibrary.cryptoBoxKeyPair(); + String hex = SodiumUtils.binary2Hex(kp.getPublicKey()); + logger.info("public key: " + hex); + byte[] publicKey = SodiumLibrary.cryptoPublicKey(kp.getPrivateKey()); + String hexPublicKey = SodiumUtils.binary2Hex(publicKey); + logger.info("calculated: " + hexPublicKey); + assertEquals(hex,hexPublicKey); + } + + @Test + public void testCryptoSecretBoxEasy() throws SodiumLibraryException + { + byte[] key = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_KEY); + byte[] nonce = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_NONCE); + byte[] message = TestVectors.MESSAGE; + byte[] cipherText = SodiumLibrary.cryptoSecretBoxEasy(message,nonce,key); + String cipherTextHex = SodiumUtils.binary2Hex(cipherText); + assertEquals(cipherTextHex,TestVectors.SECRET_BOX_CIPTHER_TEXT); + } + + @Test + public void testCryptoSecretBoxEasyEcryptDecrypt() throws SodiumLibraryException + { + long nonceBytesLength = SodiumLibrary.cryptoSecretBoxNonceBytes(); + byte[] nonceBytes = SodiumLibrary.randomBytes((int) nonceBytesLength); + String message = "This is a message"; + byte[] messageBytes = message.getBytes(); + byte[] key = SodiumLibrary.randomBytes((int) SodiumLibrary.cryptoSecretBoxKeyBytes()); + byte[] cipherText = SodiumLibrary.cryptoSecretBoxEasy(messageBytes, nonceBytes, key); + + // now decrypt + byte[] decryptedMessageBytes = SodiumLibrary.cryptoSecretBoxOpenEasy(cipherText, nonceBytes, key); + String decryptedMessage; + try + { + decryptedMessage = new String(decryptedMessageBytes, "UTF-8"); + assertEquals(message, decryptedMessage); + } catch (UnsupportedEncodingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Test + public void testCryptoSecretBoxOpenEasy() throws SodiumLibraryException + { + byte[] key = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_KEY); + byte[] nonce = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_NONCE); + byte[] cipherText = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_CIPTHER_TEXT); + byte[] message = SodiumLibrary.cryptoSecretBoxOpenEasy(cipherText,nonce,key); + String messageHex = SodiumUtils.binary2Hex(message); + assertEquals(messageHex,SodiumUtils.binary2Hex(TestVectors.MESSAGE)); + } + + @Test + public void testCryptoSecretBoxDetached() throws SodiumLibraryException + { + byte[] key = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_KEY); + byte[] nonce = SodiumUtils.hex2Binary(TestVectors.SECRET_BOX_NONCE); + byte[] message = TestVectors.MESSAGE; + SodiumSecretBox secretBox = SodiumLibrary.cryptoSecretBoxDetached(message,nonce,key); + String cipherTextHex = SodiumUtils.binary2Hex(secretBox.getCipherText()); + String macHex = SodiumUtils.binary2Hex(secretBox.getMac()); + assertEquals(TestVectors.SECRET_BOX_DETACHED_CIPHER_TEXT, cipherTextHex); + assertEquals(TestVectors.SECRET_BOX_DETACHED_MAC, macHex); + } + + @Test + public void testCryptoBoxEasy() throws SodiumLibraryException + { + /* alice encrypts with bob's public key and bob decrypts with his private key */ + SodiumKeyPair aliceKeyPair = SodiumLibrary.cryptoBoxKeyPair(); + SodiumKeyPair bobKeyPair = SodiumLibrary.cryptoBoxKeyPair(); + byte[] bobPublicKey = SodiumUtils.hex2Binary(TestVectors.CRYPTO_BOX_BOB_PUBLIC_KEY); + byte[] bobPrivateKey = SodiumUtils.hex2Binary(TestVectors.CRYPTO_BOX_BOB_PRIVATE_KEY); + + byte[] alicePublicKey = SodiumUtils.hex2Binary(TestVectors.CRYPTO_BOX_ALICE_PUBLIC_KEY); + byte[] alicePrivateKey = SodiumUtils.hex2Binary(TestVectors.CRYPTO_BOX_ALICE_PRIVATE_KEY); + + byte[] nonce = SodiumUtils.hex2Binary(TestVectors.CRYPTO_BOX_NONCE); + // Alice Encrypts with bob's public key + byte[] cipherText = SodiumLibrary.cryptoBoxEasy( + TestVectors.MESSAGE, nonce, + bobPublicKey, + alicePrivateKey); + String cipherHex = SodiumUtils.binary2Hex(cipherText); + logger.info("Ciphertext: " + cipherHex); + assertEquals(cipherHex,TestVectors.CRYPTO_BOX_ALICE_CIPHERTEXT); + + // Bob Decrypts with his Private key + byte[] decrypted = SodiumLibrary.cryptoBoxOpenEasy( + cipherText, nonce, + alicePublicKey, bobPrivateKey); + String decryptedHex = SodiumUtils.binary2Hex(decrypted); + logger.info("decrypted: " + decryptedHex); + assertEquals(TestVectors.CRYPTO_BOX_BOB_PLAINTEXT,decryptedHex); + + } + + @Test + public void testCrytoBoxSeal() throws SodiumLibraryException + { + byte[] recipientPublicKey = SodiumUtils.hex2Binary(TestVectors.SEALBOX_RECIPIENT_PUBLIC_KEY); + byte[] recipientPrivateKey = SodiumUtils.hex2Binary(TestVectors.SEALBOX_RECIPIENT_PRIVATE_KEY); + byte[] message = TestVectors.MESSAGE; + String messageHex = SodiumUtils.binary2Hex(message); + logger.info("message: " + messageHex); + logger.info("public key: " + TestVectors.SEALBOX_RECIPIENT_PUBLIC_KEY); + byte[] cipherText = SodiumLibrary.cryptoBoxSeal(message, recipientPublicKey); + String hex = SodiumUtils.binary2Hex(cipherText); + logger.info("ciphertext: " + hex); + logger.info("ct len: " + cipherText.length); + + assertNotEquals(TestVectors.SEALBOX_CIPHERTEXT, hex); + + byte[] decrypted = SodiumLibrary.cryptoBoxSealOpen(cipherText, recipientPublicKey, recipientPrivateKey); + hex = SodiumUtils.binary2Hex(decrypted); + logger.info("decrypted: " + hex); + assertEquals(messageHex,hex); + } + + /** + * Test encrypt with own public key and decrypt with own private key + * @throws SodiumLibraryException + *
+ * @author muquit@muquit.com - Mar 9, 2017 + */ + @Test + public void testEncryptDecryptOwnKeyPair() throws SodiumLibraryException + { + SodiumKeyPair aliceKp = SodiumLibrary.cryptoBoxKeyPair(); + + String secret = "this is a secret"; + byte[] cipherText = SodiumLibrary.cryptoBoxSeal(secret.getBytes(), aliceKp.getPublicKey()); + logger.info("ciphertext length: " + cipherText.length); + + byte[] plainTextBytes = SodiumLibrary.cryptoBoxSealOpen(cipherText, aliceKp.getPublicKey(), aliceKp.getPrivateKey()); + try + { + String decryptedMessage = new String(plainTextBytes, "UTF-8"); + assertEquals(secret, decryptedMessage); + } catch (UnsupportedEncodingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private byte[] makeSalt() + { + return SodiumLibrary.randomBytes(SodiumLibrary.cryptoPwhashSaltBytes()); + } + + private byte[] makeNonce() + { + return SodiumLibrary.randomBytes((int) SodiumLibrary.cryptoSecretBoxNonceBytes()); + } + + private byte[] generateKey(String password,byte[] salt) throws SodiumLibraryException + { + byte[] key = SodiumLibrary.cryptoPwhashArgon2i(password.getBytes(), salt); + return key; + } + + @Test + public void testCryptoAuth() throws SodiumLibraryException + { + byte[] key = SodiumUtils.hex2Binary(TestVectors.SECRET_KEY_AUTH_KEY); + byte[] mac = SodiumLibrary.cryptoAuth(TestVectors.MESSAGE, key); + String hex = SodiumUtils.binary2Hex(mac); + assertEquals(TestVectors.SECRET_KEY_AUTH_MAC,hex); + + boolean authenticated = SodiumLibrary.cryptoAuthVerify(mac, TestVectors.MESSAGE, key); + assertEquals(true,authenticated); + } + + @Test + public void testPasswordHash() throws SodiumLibraryException + { + TestInitializeLibrary ti = new TestInitializeLibrary(); + String passwords[] = + { + "test1", + "test2", + "test3", + "test4", + "test5", + "test6", + "test7" + }; + for (String password:passwords) + { + String hashedPassword = ti.hashPassword(password); + System.out.println(password + "->" + hashedPassword); + boolean rc = ti.check(password, hashedPassword); + assertEquals(true, rc); + } + } + + @Test + public void testEncryptPrivateKey() throws SodiumLibraryException + { + String passPhrase = "This is a passphrase"; + byte[] salt = SodiumLibrary.randomBytes(SodiumLibrary.cryptoPwhashSaltBytes()); + String hex = SodiumUtils.binary2Hex(salt); + + // create salt for derive key from pass phrase + logger.info("Generated " + salt.length + " bytes of salt"); + logger.info(hex); + logger.info("Derive key from passphrase"); + byte[] key = SodiumLibrary.cryptoPwhashArgon2i(passPhrase.getBytes(), salt); + logger.info("Dervived " + key.length + " bytes long key"); + hex = SodiumUtils.binary2Hex(key); + logger.info(hex); + + // generate key pair + logger.info("Generate key pair"); + SodiumKeyPair kp = SodiumLibrary.cryptoBoxKeyPair(); + byte[] publicKey = kp.getPublicKey(); + byte[] privateKey = kp.getPrivateKey(); + String hexPublicKey = SodiumUtils.binary2Hex(publicKey); + String hexPrivateKey = SodiumUtils.binary2Hex(privateKey); + logger.info("Generated Public key " + publicKey.length + " bytes"); + logger.info(hexPublicKey); + logger.info("Generated Private key " + privateKey.length + " bytes"); + logger.info(hexPrivateKey); + + // create nonce for encrypting private key + byte[] nonce = SodiumLibrary.randomBytes((int) SodiumLibrary.cryptoSecretBoxNonceBytes()); + hex = SodiumUtils.binary2Hex(salt); + logger.info("Generated " + nonce.length + " bytes of nonce"); + logger.info(hex); + + // encrypt the private key with nonce and key + byte[] encryptedPrivateKey = SodiumLibrary.cryptoSecretBoxEasy(privateKey, nonce, key); + // decrypt the private key again + byte[] decryptedPrivateKey = SodiumLibrary.cryptoSecretBoxOpenEasy(encryptedPrivateKey, nonce, key); + logger.info("Decrypted private key: " + SodiumUtils.binary2Hex(decryptedPrivateKey)); + + // use a wrong key, we expect decryption to fail + String wrongPassPhrase = "This is a wrong passphrase"; + key = SodiumLibrary.cryptoPwhashArgon2i(wrongPassPhrase.getBytes(), salt); + thrown.expect(SodiumLibraryException.class); + SodiumLibrary.cryptoSecretBoxOpenEasy(encryptedPrivateKey, nonce, key); + } + + @After + public void doneTesting() + { + logger.info("Done Testing Crypto"); + } + + +} diff --git a/src/test/java/test/com/muquit/libsodiumjna/TestVectors.java b/src/test/java/test/com/muquit/libsodiumjna/TestVectors.java new file mode 100755 index 0000000..258ffd6 --- /dev/null +++ b/src/test/java/test/com/muquit/libsodiumjna/TestVectors.java @@ -0,0 +1,70 @@ +package test.com.muquit.libsodiumjna; + +/** + * Test Vectors for libsodium using v1.0.11 + * Auto Generated by gen_test_vectors for libsodium-jna + * By muquit@muquit.com + */ + +public class TestVectors +{ + public static final String PASSWORD_STR = "Correct Horse Battery Staple"; + public static final String MESSAGE_STR = "this is a test"; + public static final byte[] MESSAGE = MESSAGE_STR.getBytes(); + public static final byte[] PASSWORD = PASSWORD_STR.getBytes(); + +// Password hashing Argon2 + public static final String PWHASH_ARGON2_SALT="adf7da1239a5752d37a0df6111accebe"; + public static final int PWHASH_ARGON2_SALT_LEN=16; + public static final String PWHASH_ARGON2_KEY="47a32b7dace756638785c2276f20fbdc8ec34f8d1cccdf2720605c5fc63bb7d5"; + public static final int PWHASH_ARGON2_KEY_LEN=32; + public static final String SECRET_BOX_SALT="adf7da1239a5752d37a0df6111accebe"; + +// Password storage + public static final String PWHASH_ARGON2_HASHED_PASSWORD="$argon2i$v=19$m=32768,t=4,p=1$Lq2fZ335vKkxxfKyF7Kl4g$U8NIV+STBu40qcSIkiu8jzQgrF9OTS3JnHzcUNHzOxk"; + +// Password hashing scrypt + public static final String PWHASH_SCRYPT_SALT="f7b579a1521e1bf251df44edd7cf738869b7a8623ea299c2c14bb7e719f2f584"; + public static final int PWHASH_SCRYPT_SALT_LEN=32; + public static final String PWHASH_SCRYPT_KEY="7a26fab6e75a1552c342809ef98f1a1fbb95f87ae0ba18fe039da5995513c0cc"; + public static final int PWHASH_SCRYPT_KEY_LN=32; + +// Secret-key authenticated encryption + public static final String SECRET_BOX_NONCE="aa89c537cb5ac52431bb30ffe1683db5a1703fec9e2ac50b"; + public static final int SECRET_BOX_NONCE_LEN=24; + public static final String SECRET_BOX_KEY="af43bcbea47bcde88280cdf5c19610f89e4f4baadbb779428b67bcb8a878cf3c"; + public static final int SECRET_BOX_KEY_LEN=32; + public static final String SECRET_BOX_CIPTHER_TEXT="51ad5da3e8f188c122e35bb74572c7502531c3ea4ea87410d9dea27e674f"; + public static final int SECRET_BOX_CIPTHER_TEXT_LEN=30; + public static final String SECRET_BOX_DETACHED_MAC="51ad5da3e8f188c122e35bb74572c750"; + public static final String SECRET_BOX_DETACHED_CIPHER_TEXT="2531c3ea4ea87410d9dea27e674f"; + public static final int SECRET_BOX_DETACHED_CIPHER_TEXT_LEN=14; + public static final int SECRET_BOX_DETACHED_MESSAGE_LEN=14; + +// Secret-key authentication verification + public static final String SECRET_KEY_AUTH_KEY="4bfe7c520c738f27090c0d26c90aa30adc18e9cfa02630396e82e1be21c31feb"; + public static final String SECRET_KEY_AUTH_MAC="67dc7a2ed414d0fa817cca1f02f618a381348bd48073dc6bd14c4eb90853e44c"; + +// Public-key authenticated encryption + public static final String CRYPTO_BOX_NONCE="00000000c18a57b700d06db7000000000000000098abe1bf"; + public static final int CRYPTO_BOX_NONCE_LEN=24; + public static final String CRYPTO_BOX_BOB_PUBLIC_KEY="cd2a326a0a38ea9ee6a955038b361136f81af109b6ba6e72990d55d7efdb1653"; + public static final String CRYPTO_BOX_BOB_PRIVATE_KEY="48f56925fb36932b568108359997762e8644a219230eea56936c9757a6bdfa7d"; + public static final String CRYPTO_BOX_ALICE_PUBLIC_KEY="afca62cdf90db322879ad39c13c70e7b4c32eb916455fc38bb9a60f39d344d4d"; + public static final String CRYPTO_BOX_ALICE_PRIVATE_KEY="33bc9f6ce3b32441f486100056e84a54b3fab00e0d3648b456c5e449a087f5bf"; + public static final int CRYPTO_BOX_KEY_LEN=32; + // Alice Encrypts with Bob's Public Key + public static final String CRYPTO_BOX_ALICE_CIPHERTEXT="ebccb4da876c45932a47930a305396a64ca6ef61a8ec8c9714996ea58bef"; + // Bob decrypt's with his private Key + public static final String CRYPTO_BOX_BOB_PLAINTEXT="7468697320697320612074657374"; + public static final int CRYPTO_BOX_CIPHERTEXT_LEN=30; + +// Public-key authenticated encryption + public static final String SEALBOX_RECIPIENT_PUBLIC_KEY="0120fab44ed4ccf936148436519a82e59cfa3460f00ba1cf712d5ca149e13f40"; + public static final String SEALBOX_RECIPIENT_PRIVATE_KEY="7b7faf0f4d270ccb33b2d37144fd00dad1193b884b921e776968e238471f29cd"; + public static final int SEALBOX_RECIPIENT_PUBLIC_KEY_LEN=32; + public static final int SEALBOX_RECIPIENT_PRIVATE_KEY_LEN=32; + public static final String SEALBOX_CIPHERTEXT="aba02839df12038318cdacbf99edf0fb5e8bf2421d3a809d962a6b3acf30fa1a66986ebdf8bf00cb0bc1e10e5105007602ed5794a8b75335aca18e8bb382"; + public static final String SEALBOX_DECRYPTEDTEXT="7468697320697320612074657374"; + public static final int SEALBOX_CIPHERTEXT_LEN=62; +} diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100755 index 0000000..f9f3ae7 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,6 @@ +# ref: https://www.youtube.com/watch?v=tacyRereiQI +log4j.rootLogger=DEBUG, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %5p %c:%L - %m%n \ No newline at end of file