diff --git a/.classpath b/.classpath new file mode 100755 index 0000000..e7e9144 --- /dev/null +++ b/.classpath @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..e11ca06 --- /dev/null +++ b/.project @@ -0,0 +1,36 @@ + + + libsodium-jna + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 0000000..443e085 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/ChangeLog.txt b/ChangeLog.txt new file mode 100644 index 0000000..ba9fc61 --- /dev/null +++ b/ChangeLog.txt @@ -0,0 +1,5 @@ +# v1.01 + +* Throw SodiumLibraryException in case of error. Before RuntimeException was thrown for everything. + (Jan-31-2017) + diff --git a/README.md b/README.md new file mode 100755 index 0000000..2d7dd37 --- /dev/null +++ b/README.md @@ -0,0 +1,675 @@ + + +- [Introduction](#introduction) +- [Supported platforms](#supported-platforms) +- [Requirements](#requirements) +- [How to use](#how-to-use) + - [Install native libsodium C library first](#install-native-libsodium-c-library--first) + - [Install ```libsodium-jna```](#install-libsodium-jna) + - [Update your project's ```pom.xml```](#update-your-projects-pomxml) +- [Supported APIs](#supported-apis) + - [Version of sodium library](#version-of-sodium-library) + - [Generating random data](#generating-random-data) + - [Secret-key cryptography](#secret-key-cryptography) + - [Public-key cryptography](#public-key-cryptography) + - [Password hashing, Key generation](#password-hashing-key-generation) +- [APIs](#apis) + - [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + - [Version of sodium library](#version-of-sodium-library-1) + - [Example:](#example) + - [Generate random data](#generate-random-data) + - [Example](#example) + - [Secret-key authenticated cryptography](#secret-key-authenticated-cryptography) + - [Encrypt a message with a key and a nonce](#encrypt-a-message-with-a-key-and-a-nonce) + - [Verify and decrypt the message](#verify-and-decrypt-the-message) + - [Example](#example-1) + - [Public-key cryptography](#public-key-cryptography-1) + - [Generate Key Pair](#generate-key-pair) + - [Example](#example-2) + - [Example: Alice (sender) Encrypts with Bob's (recipient) public key and creates authentication tag with her private key](#example-alice-sender-encrypts-with-bobs-recipient-public-key-and-creates-authentication-tag-with-her-private-key) + - [Example: Bob (recipient) verifies with Alice's (sender) public key and decrypts with his public key](#example-bob-recipient-verifies-with-alices-sender-public-key-and-decrypts-with-his-public-key) + - [Example: Alice (sender) anonymously encrypts message with Bob's (recipient) public key](#example-alice-sender-anonymously-encrypts-message-with-bobs-recipient-public-key) + - [Example: Bob (recipient) decrypts the message with his private key](#example-bob-recipient-decrypts-the-message-with-his-private-key) + - [Password hashing, key generation](#password-hashing-key-generation) + - [Derive Key from password](#derive-key-from-password) + - [Example:](#example-1) + - [Derive US-ASCII encoded key from password for storing](#derive-us-ascii-encoded-key-from-password-for-storing) + - [Example:](#example-2) + - [Verify Stored US-ASCII encoded key with password](#verify-stored-us-ascii-encoded-key-with-password) + - [Example](#example-3) + - [How to encrypt a private key](#how-to-encrypt-a-private-key) + - [Encrypting the private key](#encrypting-the-private-key) + - [Decrypting the private key](#decrypting-the-private-key) +- [If your project is not a maven project](#if-your-project-is-not-a-maven-project) +- [License is MIT](#license-is-mit) + + + +# Introduction + +*libsodium-jna* is a java library that binds to [libsodium](https://libsodium.org) C crypto APIs with [Java Native Access](https://github.com/java-native-access/jna) (JNA). I wrote it because I did not like any of the Java implementation of libsodium. I hope you will find this project useful and fun to use. + +Bug reports, suggestions are always welcome! + +If you add support to more libsodium APIs, please send me a pull request. If yo do so, please do not forget to update the documentation add unit tests. If you need tog generate test vectors, please look at ```misc/gen_test_vectors.c``` + +# Supported platforms + +In theory it should work on any platform where native libsodium library works and JVM 1.7+ is available. + +The implementation is tested on the following platforms with libsodium v1.0.12 with JVM 1.7 and 1.8. + +| Platform | JVM | +|----------|-----| +| 64 bit Windows 7 and 10 | 64 bit JVM | +| 64 bit MacOS X running El Capital | 64 bit JVM | +| 64 bit Linux | 64 bit JVM | +| 32 bit Linux | 32 bit JVM | + +# Requirements + +* jdk 1.7+ + +* maven must be installed in order to create the jar file. However, it is possible to use the library in a +non-maven project. + +* [libsodium](https://libsodium.org) 1.0.11 or higher. libsodium-jna itself does not enforce version checking but make sure you are using libsodium v 1.0.11 or higher. + +* Make sure native [libsodium](https://libsodium.org) is already installed in the system. This library does not come with native version of libsodium. *It is a good idea to compile and install [libsodium](https://libsodium.org) yourself instead of using one from the Internet*. + +* This library does not load any libsodium library from path, rather you have to specify exactly where the library is located. + +# How to use +## Install native libsodium C library first + +* Compile and Install libsodium. It is a requirement. + * Download [libsodium-1.0.12.tar.gz](https://download.libsodium.org/libsodium/releases/) + * make sure ```pkg-config``` is installed + +Follow the instructions on [libsodium doc](https://download.libsodium.org/doc/) page on how to compile and install. I do the following on Linux and Mac OS X: + +``` + tar -xf libsodium-1.0.12.tar.gz + cd libsodium-1.0.12 + ./configure + make && make check + sudo make install + sudo ldconfig +``` + +## Install ```libsodium-jna``` + +At this time, *libsodium-jna* is not in maven central. Therefore, before using it, it must be installed first. + +``` + git clone git@github.com:muquit/libsodium-jna.git + cd libsodium-jna + mvn clean install + mvn test +``` + +To load the project in Eclipse, select _File->Import...->Maven->Existing Maven Projects_, then Click on *Next >*, click on *Browse...* button and select the libsodium-jna directory. + +## Update your project's ```pom.xml``` + +Add the following block inside dependencies block: + +``` + + + com.muquit.libsodiumjna + libsodium-jna + 1.0.1 + + +``` +# Supported APIs + +Before making any API calls, native sodium must be loaded from a specific path. Please look at the section [Load the libsodium C Library first](#load-the-libsodium-c-library-first) for details. + +The following APIs are implemented at this time. + +## Version of sodium library + +| native libsodium C API| java binding |Description| +|-----------------------|--------------|-----------| +|```sodium_version_string()```|```libsodiumVersionString()```|Return the version of native sodium library| + +## Generating random data + +| native libsodium C API| java binding |Description| +|-----------------------|--------------|-----------| +|```randombytes_buf()```|```randomBytes()```|Reruns specified number of unpredictable sequence of bytes| + +## Secret-key cryptography + +| native libsodium C API| java binding |Description| +|-----------------------|--------------|-----------| +|[```crypto_secretbox_easy()```](https://download.libsodium.org/doc/secret-key_cryptography/authenticated_encryption.html)| ```public static byte[] cryptoSecretBoxEasy(byte[] message, byte[] nonce, byte[] key)``` |Encrypts a message with a key and a nonce| +|[```crypto_secretbox_open_easy()```](https://download.libsodium.org/doc/secret-key_cryptography/authenticated_encryption.html)|```public static byte[] cryptoSecretBoxOpenEasy(byte[] cipherText,byte[] nonce, byte[] key)```|Verifies and decrypts an encrypted message produced by ```cryptoSecretBoxEasy()```| +|```crypto_secretbox_detached()```|```public static SodiumSecretBox cryptoSecretBoxDetached(byte[] message, byte[] nonce, byte[] key)```|Encrypts a message with a key and a nonce and returns authentication tag with the encrypted message| +|```crypto_secretbox_open_detached()```|```public static byte[] cryptoSecretBoxOpenDetached(SodiumSecretBox secretBox, byte[] nonce, byte[] key)```|Verifies and decrypts an encrypted message, authentication tag used in ```cryptoSecretBoxDetached()``` is needed| +|```crypto_auth()```|```public static byte[] cryptoAuth(byte[] message, byte[] key)```|Computes an authentication tag for a message | +|```crypto_auth_verify()```|```public static boolean cryptoAuthVerify(byte[] mac, byte[] message, byte[] key)```|Verifies the authentication tag for a message| +|```crypto_secretbox_keybytes()```|```public static long cryptoSecretBoxKeyBytes()```|Length of key| +|```crypto_secretbox_noncebytes()```|```public static long cryptoSecretBoxNonceBytes()```|Length of nonce| +|```crypto_secretbox_macbytes()```|```public static long cryptoSecretBoxMacBytes()```|Length of authentication code| + +## Public-key cryptography + +| native libsodium C API| java binding |Description| +|-----------------------|--------------|-----------| +|[```crypto_box_keypair()```](https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html)|[```public static SodiumKeyPair cryptoBoxKeyPair()```](#generate-key-pair)|Randomly generate a private key and a corresponding public key | +|```crypto_scalarmult_base()```|```public static byte[] cryptoPublicKey(byte[] privateKey)```|Compute public key given a private key| +|```crypto_box_easy()```|```public static byte[] cryptoBoxEasy(byte[] message, byte[] nonce, byte[] publicKey, byte[] privateKey)```|Encrypts a message with a recipient's public key, sender's private key and a nonce| +|```crypto_box_open_easy()```|```public static byte[] cryptoBoxOpenEasy(byte[] cipherText, byte[]nonce, byte[] publicKey, byte[] privateKey)```|Verifies and decrypts an encrypted message produced by ```cryptoBoxEasy()```| +|```crypto_box_seal()```|```public static byte[] cryptoBoxSeal(byte[] message, byte[] recipientPublicKey)```|Encrypts a message for a recipient with recipient's public key| +|```crypto_box_seal_open()```|```public static byte[] cryptoBoxSealOpen(byte[] cipherText,byte[] pk, byte[] sk)```|Decrypts an encrypted message with the key pair public key and private key| +|```crypto_box_noncebytes()```|```public static long cryptoBoxNonceBytes()```|Length of nonce| +|```crypto_box_seedbytes()```| ```public static long crytoBoxSeedBytes()```|Length of key seed| +|```crypto_box_publickeybytes()```|```public static long crytoBoxPublicKeyBytes()```|Length of public key| +|```crypto_box_secretkeybytes()```|```public static long crytoBoxSecretKeyBytes()```|Length of private key| +|```crypto_box_macbytes()```|```public static long cryptoBoxMacBytes()```|Length of mac| +|```crypto_box_sealbytes()```|```public static long cryptoBoxSealBytes()```|Length of seal| + +## Password hashing, Key generation + +| native libsodium C API| java binding |Description| +|-----------------------|--------------|-----------| +|[```crypto_pwhash()```](https://download.libsodium.org/doc/password_hashing/the_argon2i_function.html)| ```public static byte[] cryptoPwhash(byte[] passwd, byte[] salt, long opsLimit, NativeLong memLimit, int algorithm)```|Derives a key from a password. The ```salt```, ```opslimit```, ```memlimit``` and ```algorithm``` can be specified. +| | ```public static byte[] cryptoPwhashArgon2i(byte[] passwd, byte[] salt)``` |Derives a key from a password using Argon2 memory-hard function. Uses default values for opsLimit and memLimit| +| | ```public static byte[] cryptoPwhashScrypt(byte[] passwd, byte[] salt)``` |Derives a key from a password using Scrypt| +|```crypto_pwhash_str()```|```public static String cryptoPwhashStr(byte[] password)```|Derives a US ASCII encoded key from a password using memory-hard, CPU-intensive hash function| +|```cryupto_pwhash_str_verify()```|```public static boolean cryptoPwhashStrVerify(String usAsciiKey, byte[] password)```|Verifies a US ASCII encoded password string generated by ```cryptoPwhashStr()```| +|```crypto_pwhash_scryptsalsa208sha256()```|```public static byte[] cryptoPwhashScryptSalsa208Sha256(byte[] key, byte[] passwd, byte[] salt, long opslimit, NativeLong memlimit)```| +|```crypto_pwhash_alg_argon2i13()```|```public static int cryptoPwhashAlgArgon2i13()```|-| +|```crypto_pwhash_alg_default()```|```public static int cryptoPwhashAlgDefault()```|-| +|```crypto_pwhash_saltbytes()```|```public static int cryptoNumberSaltBytes()```|Length of salt| +|```crypto_pwhash_saltbytes()```|```public static int cryptoPwhashSaltBytes()```|Length of salt| +|```crypto_pwhash_opslimit_interactive()```|```public static long cryptoPwHashOpsLimitInteractive()```|-| +|```crypto_pwhash_memlimit_interactive()```|```public static NativeLong cryptoPwHashMemLimitInterative()```|-| +|```crypto_pwhash_scryptsalsa208sha256_saltbytes()```|```public static long cryptoPwHashScryptSalsa208Sha256SaltBytes()```|-| + +# APIs + +## Load the libsodium C Library first + +Before making call to any API, the native *sodium C library* must be loaded explicitly from a specific path. It is possible to load the library +from path, however *libsodium-jna* is designed to load *sodium library* explicitly from a path. + +Example on how to load the libsodium first: + +```java + private static String libraryPath = null; + + if (Platform.isMac()) + { + // MacOS + libraryPath = "/usr/local/lib/libsodium.dylib"; + libraryPath = libraryPath; + logger.info("Library path in Mac: " + libraryPath); + } + else if (Platform.isWindows()) + { + // Windows + libraryPath = "C:/libsodium/libsodium.dll"; + logger.info("Library path in Windows: " + libraryPath); + } + else + { + // Linux + libraryPath = "/usr/local/lib/libsodium.so"; + logger.info("Library path: " + libraryPath); + } + + logger.info("loading libsodium..."); + SodiumLibrary.setLibraryPath(libraryPath); + // To check the native library is actually loaded, print the version of + // native sodium library + String v = SodiumLibrary.libsodiumVersionString(); + logger.info("libsodium version: " + v); +} +``` +If the library could not be loaded Java's ```RuntimeException``` will be thrown. +If the library is loaded successfully, you are ready to make the API calls. + +Please look at ```TestInitializeLibrary.java``` to see how the library can be initialized from a static block. + +## Version of sodium library + +```Java +/** + * Return the version of native sodium library. After loading the native C library, it is a good idea + * to make this call to make sure that the expected version of the sodium library is loaded. + */ +public static String libsodiumVersionString() +``` + +### Example: + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) +```java + logger.info("libsodium version: " + SodiumLibrary.libsodiumVersionString()); +``` + +## Generate random data + +```java +/** + * 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. + * + * Parameters: + * size Number of random bytes to generate + * + * Return: + * Array of random bytes + */ + public static byte[] randomBytes(int size) +``` + +### Example + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +```java +// generate 16 bytes of random data +byte[] randomBytes = SodiumLibrary.randomBytes(16); +String hex = SodiumUtils.binary2Hex(salt); + +// generate libsodium's standard number of salt bytes +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); +``` + +## Secret-key authenticated cryptography + + Excerpt from libsodium documentation: + +> This operation: +> * Encrypts a message with a key and a nonce to keep it confidential +> * Computes an authentication tag. This tag is used to make sure that the message hasn't been tampered with before decrypting it. + +> A single key is used both to encrypt/sign and verify/decrypt messages. For this reason, it is critical to keep the key confidential. +> The nonce doesn't have to be confidential, but it should never ever be reused with the same key. The easiest way to generate a nonce is to use randombytes_buf(). + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +### Encrypt a message with a key and a nonce + +```java +/** + * Encrypts a message with a key and a nonce + * + * Parameters: + * message message bytes to encrypt + * nonce nonce bytes. Generate it by calling public static long cryptoBoxNonceBytes() + * + * Returns: + * Encrypted cipher text bytes + * + * Throws SodiumLibraryException in case of error + */ + +public static byte[] crytoSecretBoxEasy(byte[] message, byte[] nonce, byte[] key) +``` + +### Verify and decrypt the message + +```java +/** + * Verify and decrypt the message encrypted with crytoSecretBoxEasy() + * + * Parameters: + * cipherText - encrypted bytes + * nonce - nonce bytes used during encryption + * key - key bytes used in encryption + * + * Returns: + * Decrypted message bytes + * + * Throws SodiumLibraryException in case of error + */ +public static byte[] cryptoSecretBoxOpenEasy(byte[] cipherText,byte[] nonce, byte[] key) +``` + +### Example + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +```java + // don't forget to load the libsodium library first + + String message = "This is a message"; + + // generate nonce + long nonceBytesLength = SodiumLibrary.cryptoSecretBoxNonceBytes(); + byte[] nonceBytes = SodiumLibrary.randomBytes((int) nonceBytesLength); + byte[] messageBytes = message.getBytes(); + + // generate the encryption key + byte[] key = SodiumLibrary.randomBytes((int) SodiumLibrary.cryptoSecretBoxKeyBytes()); + + // encrypt + 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"); + System.out.println("Decrypted message: " + decryptedMessageBytes); + } catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } +``` + +## Public-key cryptography +### Generate Key Pair + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +```java +/** + * Randomly generates a secret key and a corresponding public key + * + * Return: + * SodiumKeyPair + */ + public static SodiumKeyPair cryptoBoxKeyPair() +``` + +### Example + +```java + +// 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); +``` + +### Example: Alice (sender) Encrypts with Bob's (recipient) public key and creates authentication tag with her private key + +```java +/** + * Alice encrypts a message with recipient's (Bob) public key and + * creates authentication tag with her private key + * + * Parameters: + * message The message to encrypt + * nonce SodiumLibrary.crytoBoxNonceBytes() bytes of nonce. It must be preserved + * because it will be needed during decryption + * recipientPublicKey Recipient's public key for encrypting the message + * senderPrivateKey Sender's private key for creating authentication tag + * + * Return: + * encrypted message as an array of bytes + */ +public static byte[] cryptoBoxEasy(byte[] message, + byte[] nonce + byte[] recipientPublicKey, byte[] senderPrivateKey) throws SodiumLibraryException +``` + +### Example: Bob (recipient) verifies with Alice's (sender) public key and decrypts with his public key + +```java +/** + * Bob (recipient) verifies the message with Alice's (sender) public key and + * decrypts the message with his private key + * + * Parameters: + * cipherText Message to decrypt + * nonce Nonce used during encryption + * senderPublicKey Sender's (Alice) public key for verifying message + * recipientPrivateKey Recipient's (Bob) Private key to decrypt the message + * + * Returns: + * Decrypted message as an array of bytes. + * In case of error SodiumLibraryException() run time exception will be thrown + */ +public static byte[] cryptoBoxOpenEasy(byte[] cipherText, + byte[]nonce, + byte[] senderPublicKey, byte[] recipientPrivateKey) throws SodiumLibraryException +``` + +### Example: Alice (sender) anonymously encrypts message with Bob's (recipient) public key + +TODO + +### Example: Bob (recipient) decrypts the message with his private key + +TODO + +## Password hashing, key generation + +From [libsodium documentation](https://download.libsodium.org/doc/password_hashing/): + +> Password hashing functions derive a secret key of any size from a password and a salt. + +* The generated key has the size defined by the application, no matter what the password length is. +* The same password hashed with same parameters will always produce the same output. +* The same password hashed with different salts will produce different outputs. +* The function deriving a key from a password and a salt is CPU intensive and intentionally requires a fair amount of memory. Therefore, it mitigates brute-force attacks by requiring a significant effort to verify each password. + + +### Derive Key from password + +```java +/** + * Derives a key from a password and the given salt using Argon2i + * + * Parameters: + * password The password + * salt The salt. The salt must be SodiumLibrary.cryptoPwhashSaltBytes() bytes long + * + * Comments: + * This method uses: + * opslimit as crypto_pwhash_opslimit_interactive() + * memlimit as crypto_pwhash_memlimit_interactive() + * algorithm as crypto_pwhash_alg_argon2i13() + * + * Returns: + * The derived key as array of bytes. + * In case of error SodiumLibraryException() run time exception will be thrown + */ +public static byte[] cryptoPwhashArgon2i(byte[] passwd, byte[] salt) throws SodiumLibraryException +``` + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +### Example: + +``` + String passPhrase = "This is a passphrase"; + byte[] salt = SodiumLibrary.randomBytes(SodiumLibrary.cryptoPwhashSaltBytes()); + String hex = SodiumUtils.binary2Hex(salt); + + // create salt for deriving key from the pass phrase + // salt is public but needs to be saved + 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("Derived " + key.length + " bytes long key"); + hex = SodiumUtils.binary2Hex(key); + logger.info(hex); + + // Later when you need to derive the key from the passphrase, use the saved salt +``` + +### Derive US-ASCII encoded key from password for storing + +```java +/** + * Returns a US-ASCII encoded key derived from the password. The key can be stored for + * verification. + * + * Parameters: + * password The password + * + * Comments: + * Memory-hard, CPU-intensive hash function is applied to the + * password in key generation process. + * + * Automatically generated salt is used in the key generation + * + * Uses opslimit as crypto_pwhash_opslimit_interactive() + * Uses memlimit as crypto_pwhash_memlimit_interactive() + * + * Returns: + * derived key as US-ASCII encoded string + */ +public static String cryptoPwhashStr(byte[] password) throws SodiumLibraryException +``` + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +### Example: + +``` + 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); +``` + +### Verify Stored US-ASCII encoded key with password + +```java +/** + * Verify a US-ASCII encoded key derived previously by calling + * cryptoPwhashStr() + * + * Parameters: + * key US-ASCII encoded key to verify + * password The password + * + * Returns: + * true if the key can be verified, false otherwise + */ +public static boolean cryptoPwhashStrVerify(String usAsciiKey, + byte[] password) +``` + +Make sure to [Load the libsodium C Library first](#load-the-libsodium-c-library-first) + +### Example +``` + 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); + // verify the password + boolean rc = SodiumLibrary.cryptoPwhashStrVerify(key, passwordBytes); + if (rc) + { + logger.info("Password is verified"); + } +``` + + +## How to encrypt a private key + +This instruction is for encrypting something with a symmetric memory hardened key derived from a passphrase and use that key in encryption. I am just giving the example on encrypting a private key as this is a very common requirement. + +When the public and private key pair is generated and it is required to store the private key, the private key must be properly encrypted and stored. +The following procedure can be used to encrypt the private key: + +### Encrypting the private key +* Come up with a _strong_ pass phrase. Use a long memorable pass phrase. This is the most important defense against brute force +cracking. + +* Generate cryptoPwhashSaltBytes() long salt + +```java + byte[] salt = SodiumLibrary.randomBytes(SodiumLibrary.cryptoPwhashSaltBytes); +``` + +* Derive a brute force resistant key from the pass phrase + +```java + byte[] key = SodiumLibrary.cryptoPwhashArgon2i(passPhrase, salt); +``` + +* _Store_ the salt. This will be used to derive the same key again from the pass phrase + +* Generate cryptoSecretBoxNonceBytes() long nonce + +```java + byte[] nonce = SodiumLibrary.randomBytes(SodiumLibrary.cryptoSecretBoxNonceBytes()); +``` + +* Encrypt the private key + +```java + byte[] encryptedPrivateKey = SodiumLibrary.cryptoSecretBoxEasy(privateKey, nonce, key); +``` + +* _Store_ nonce and the encrypted private key + +### Decrypting the private key + +* Derive the encryption key from the pass phrase and stored salt + +```java + byte[] key = SodiumLibrary.cryptoPwhashArgon2i(passPhraseBytes, saltBytes); +``` + +* Verify and decrypt the private key with the derived key and stored nonce + +```java + byte[] privateKey = SodiumLibrary.cryptoSecretBoxOpenEasy(encryptedPrivateKey, nonce, key); +``` + +# If your project is not a maven project + +If your project is not a maven project, find out the dependencies of libsodium-jna and obtain the jar files from maven +central manually and add them to your build path + +* find the dependencies + +``` + $ cd libsodium-jna + $ mvn dependency:tree +... +[INFO] ------------------------------------------------------------------------ +[INFO] Building com.muquit.libsodiumjna 1.0.1 +[INFO] ------------------------------------------------------------------------ +[INFO] +[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ libsodium-jna --- +[INFO] com.muquit.libsodiumjna:libsodium-jna:jar:1.0.1 +[INFO] +- net.java.dev.jna:jna:jar:4.2.2:compile +[INFO] +- org.slf4j:slf4j-api:jar:1.7.21:compile +[INFO] +- org.slf4j:slf4j-log4j12:jar:1.7.21:compile +[INFO] | \- log4j:log4j:jar:1.2.17:compile +[INFO] +- commons-codec:commons-codec:jar:1.10:compile +[INFO] \- junit:junit:jar:4.11:test +[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test +... +``` + +# License is MIT + +MIT diff --git a/misc/Makefile b/misc/Makefile new file mode 100644 index 0000000..35e55fb --- /dev/null +++ b/misc/Makefile @@ -0,0 +1,26 @@ +rm=/bin/rm -f +CC= cc +DEFS= +PROGNAME= gen_test_vectors +INCLUDES= -I. +LIBS=`pkg-config --libs libsodium` + + +DEFINES= $(INCLUDES) $(DEFS) -DSYS_UNIX=1 +CFLAGS= -g $(DEFINES) `pkg-config --cflags libsodium` + +SRCS = gen_test_vectors.c + +OBJS = gen_test_vectors.o + +.c.o: + $(rm) $@ + $(CC) $(CFLAGS) -c $*.c + +all: $(PROGNAME) + +$(PROGNAME) : $(OBJS) + $(CC) $(CFLAGS) -o $(PROGNAME) $(OBJS) $(LIBS) + +clean: + $(rm) $(OBJS) $(PROGNAME) core *~ diff --git a/misc/extract_declarations.rb b/misc/extract_declarations.rb new file mode 100755 index 0000000..dbcef72 --- /dev/null +++ b/misc/extract_declarations.rb @@ -0,0 +1,47 @@ +#!/usr/bin/env ruby + +# +# extract funcition declarations from sodium header files +# muquit@muquit.com +class ExtractFuncs + def initialize + @regex = /^int|^size_t|^const|^unsigned|^long|^char.+$/ + end + + def doit + ARGV.each do |file| + lines = File.readlines(file) + lines.each_with_index do |val,idx| + line = lines[idx] + line.chomp! if line + next if line.length == 0 + if line =~ @regex + if line =~ /;$/ + puts line + next + else + puts "#{line}" + idx = idx + 1 + line = lines[idx]; + while line !~ /\;$/ + idx = idx + 1 + l = lines[idx]; + if l =~ /attribute.+;$/ + puts ";" + else + puts "#{l}" + end + if l =~ /\;$/ + break + end + end + end + end + end + end + end +end + +if __FILE__ == $0 + ExtractFuncs.new().doit() +end diff --git a/misc/gen_test_vectors.c b/misc/gen_test_vectors.c new file mode 100644 index 0000000..9f6b7c1 --- /dev/null +++ b/misc/gen_test_vectors.c @@ -0,0 +1,394 @@ +/* +** Generate Test Vectors for libsodium. They are used in TestVector.java +** in my libsodium-jna implemetation. The vectors are generated on a +** 32 bit Linux system. +** muquit@muquit.com Oct-10-2016 - first cut +*/ +#include +#include +#include + +#include + +#define PASSWORD "Correct Horse Battery Staple" +#define MESSAGE ((const unsigned char *) "this is a test") + +/* +** Just print bytes in hex +*/ +static char *hexstr(unsigned char *data, int data_len) +{ + int hex_len = data_len * 2 + 1; + char *hex = (char *) malloc(hex_len * sizeof(char)); + memset(hex,0,hex_len); + sodium_bin2hex(hex,hex_len,data,data_len); + return(hex); +} + +static void print_footer(void) +{ + printf("}\n"); +} + +static void print_header() +{ + printf( +"package test.com.muquit.libsodiumjna;\n\n" +"/**\n" +" * Test Vectors for libsodium using v%s\n" +" * Auto Generated by gen_test_vectors for libsodium-jna\n" +" * By muquit@muquit.com\n" +" */\n\n" +"public class TestVectors\n" +"{\n" +" public static final String PASSWORD_STR = \"%s\";\n" +" public static final String MESSAGE_STR = \"%s\";\n" +" public static final byte[] MESSAGE = MESSAGE_STR.getBytes();\n" +" public static final byte[] PASSWORD = PASSWORD_STR.getBytes();\n", + sodium_version_string(), + PASSWORD,MESSAGE); + +} + +static void print_pwhash_argon2_vectors(void) +{ + int + rc; + unsigned char + salt[crypto_pwhash_SALTBYTES], + key[crypto_box_SEEDBYTES]; + + randombytes_buf(salt, sizeof(salt)); + rc = crypto_pwhash(key, sizeof(key), + PASSWORD, strlen(PASSWORD), + salt, + crypto_pwhash_OPSLIMIT_INTERACTIVE, + crypto_pwhash_MEMLIMIT_INTERACTIVE, + crypto_pwhash_ALG_DEFAULT); + assert(rc == 0); + + + + printf( +"\n" +"// Password hashing Argon2\n" +" public static final String PWHASH_ARGON2_SALT=\"%s\";\n" +" public static final int PWHASH_ARGON2_SALT_LEN=%d;\n" +" public static final String PWHASH_ARGON2_KEY=\"%s\";\n" +" public static final int PWHASH_ARGON2_KEY_LEN=%d;\n" +" public static final String SECRET_BOX_SALT=\"%s\";\n", + hexstr(salt,sizeof(salt)), + sizeof(salt), + hexstr(key,sizeof(key)), + sizeof(key), + hexstr(salt,sizeof(salt))); +} + +static void print_pwhash_argon2_password_storage_vectors(void) +{ + char + hashed_password[crypto_pwhash_STRBYTES]; + int + rc; + + rc = crypto_pwhash_str(hashed_password, + PASSWORD, strlen(PASSWORD), + crypto_pwhash_OPSLIMIT_INTERACTIVE, + crypto_pwhash_MEMLIMIT_INTERACTIVE); + assert(rc == 0); + printf( +"\n" +"// Password storage\n" +" public static final String PWHASH_ARGON2_HASHED_PASSWORD=\"%s\";\n", + hashed_password); + +} +static void print_pwhash_scrypt_vectors(void) +{ + int + rc; + + unsigned char + salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES], + key[crypto_box_SEEDBYTES]; + + memset(key,0,sizeof(key)); + randombytes_buf(salt, sizeof salt); + + rc = crypto_pwhash_scryptsalsa208sha256(key, sizeof(key), + PASSWORD, strlen(PASSWORD), + salt, + crypto_pwhash_OPSLIMIT_INTERACTIVE, + crypto_pwhash_MEMLIMIT_INTERACTIVE); + assert(rc == 0); + + printf( +"\n" +"// Password hashing scrypt\n" +" public static final String PWHASH_SCRYPT_SALT=\"%s\";\n" +" public static final int PWHASH_SCRYPT_SALT_LEN=%d;\n" +" public static final String PWHASH_SCRYPT_KEY=\"%s\";\n" +" public static final int PWHASH_SCRYPT_KEY_LN=%d;\n", + hexstr(salt,sizeof(salt)), + sizeof(salt), + hexstr(key,sizeof(key)), + sizeof(key)); + +} + +static void print_secret_key_vectors(void) +{ + int + rc; + unsigned char + nonce[crypto_secretbox_NONCEBYTES], + key[crypto_secretbox_KEYBYTES], + mac[crypto_secretbox_MACBYTES], + *ciphertext = NULL; + + int + ciphertext_len; + + + ciphertext_len = (crypto_secretbox_MACBYTES + strlen(MESSAGE)) * sizeof(unsigned char); + ciphertext = (unsigned char *) malloc(ciphertext_len); + assert(ciphertext != NULL); + memset(ciphertext, 0, ciphertext_len); + + randombytes_buf(nonce, sizeof (nonce)); + randombytes_buf(key, sizeof (key)); + + rc = crypto_secretbox_easy(ciphertext, MESSAGE, strlen(MESSAGE), nonce, key); + printf( +"\n" +"// Secret-key authenticated encryption\n" +" public static final String SECRET_BOX_NONCE=\"%s\";\n" +" public static final int SECRET_BOX_NONCE_LEN=%d;\n" +" public static final String SECRET_BOX_KEY=\"%s\";\n" +" public static final int SECRET_BOX_KEY_LEN=%d;\n" +" public static final String SECRET_BOX_CIPTHER_TEXT=\"%s\";\n" +" public static final int SECRET_BOX_CIPTHER_TEXT_LEN=%d;\n", + hexstr(nonce,sizeof(nonce)), + sizeof(nonce), + hexstr(key,sizeof(key)), + sizeof(key), + hexstr(ciphertext,ciphertext_len), + ciphertext_len); + if (ciphertext) + (void) free((char *) ciphertext); + assert(rc == 0); + + /* detached mode */ + ciphertext_len = (strlen(MESSAGE)) * sizeof(unsigned char); + ciphertext = (unsigned char *) malloc(ciphertext_len); + assert(ciphertext != NULL); + + rc = crypto_secretbox_detached(ciphertext, mac, + MESSAGE, strlen(MESSAGE), + nonce,key); + assert(rc == 0); + printf( +" public static final String SECRET_BOX_DETACHED_MAC=\"%s\";\n" +" public static final String SECRET_BOX_DETACHED_CIPHER_TEXT=\"%s\";\n" +" public static final int SECRET_BOX_DETACHED_CIPHER_TEXT_LEN=%d;\n" +" public static final int SECRET_BOX_DETACHED_MESSAGE_LEN=%d;\n", + hexstr(mac,sizeof(mac)), + hexstr(ciphertext,ciphertext_len), + ciphertext_len, + strlen(MESSAGE)); +} + +static void print_secret_key_auth_vectors(void) +{ + int + rc; + + unsigned char + key[crypto_auth_KEYBYTES], + mac[crypto_auth_BYTES]; + + randombytes_buf(key, sizeof(key)); + rc = crypto_auth(mac, MESSAGE, strlen(MESSAGE), key); + assert(rc == 0); + + rc = crypto_auth_verify(mac, MESSAGE, strlen(MESSAGE), key); + assert(rc == 0); + printf( +"\n" +"// Secret-key authentication verification\n" +" public static final String SECRET_KEY_AUTH_KEY=\"%s\";\n" +" public static final String SECRET_KEY_AUTH_MAC=\"%s\";\n", + hexstr(key,sizeof(key)), + hexstr(mac,sizeof(mac))); + +} + +static void print_public_key_vectors(void) +{ + int + rc; + + unsigned char + nonce[crypto_box_NONCEBYTES], + *ciphertext = NULL, + *decrypted = NULL, + alice_publickey[crypto_box_PUBLICKEYBYTES], + alice_secretkey[crypto_box_SECRETKEYBYTES], + bob_publickey[crypto_box_PUBLICKEYBYTES], + bob_secretkey[crypto_box_SECRETKEYBYTES]; + + int + ciphertext_len; + + ciphertext_len = (crypto_secretbox_MACBYTES + strlen(MESSAGE)) * sizeof(unsigned char); + ciphertext = (unsigned char *) malloc(ciphertext_len); + assert(ciphertext != NULL); + + rc = crypto_box_keypair(alice_publickey, alice_secretkey); + assert(rc == 0); + + rc = crypto_box_keypair(bob_publickey, bob_secretkey); + assert(rc == 0); + + /* alice entrypts with bob's public key */ + rc = crypto_box_easy(ciphertext, MESSAGE, strlen(MESSAGE), + nonce, + bob_publickey, alice_secretkey); + assert(rc == 0); + + decrypted = (unsigned char *) malloc(strlen(MESSAGE) * sizeof(unsigned char *)); + assert(decrypted != NULL); + + /* bob decrypts with his private key */ + rc = crypto_box_open_easy(decrypted, + ciphertext, ciphertext_len, + nonce, + alice_publickey, bob_secretkey); + assert(rc == 0); + + + printf( +"\n" +"// Public-key authenticated encryption\n" +" public static final String CRYPTO_BOX_NONCE=\"%s\";\n" +" public static final int CRYPTO_BOX_NONCE_LEN=%d;\n" +" public static final String CRYPTO_BOX_BOB_PUBLIC_KEY=\"%s\";\n" +" public static final String CRYPTO_BOX_BOB_PRIVATE_KEY=\"%s\";\n" +" public static final String CRYPTO_BOX_ALICE_PUBLIC_KEY=\"%s\";\n" +" public static final String CRYPTO_BOX_ALICE_PRIVATE_KEY=\"%s\";\n" +" public static final int CRYPTO_BOX_KEY_LEN=%d;\n" +" // Alice Encrypts with Bob's Public Key\n" +" public static final String CRYPTO_BOX_ALICE_CIPHERTEXT=\"%s\";\n" +" // Bob decrypt's with his private Key\n" +" public static final String CRYPTO_BOX_BOB_PLAINTEXT=\"%s\";\n" +" public static final int CRYPTO_BOX_CIPHERTEXT_LEN=%d;\n", + hexstr(nonce,sizeof(nonce)), + sizeof(nonce), + hexstr(bob_publickey,sizeof(bob_publickey)), + hexstr(bob_secretkey,sizeof(bob_secretkey)), + hexstr(alice_publickey,sizeof(alice_publickey)), + hexstr(alice_secretkey,sizeof(alice_secretkey)), + sizeof(bob_publickey), + hexstr(ciphertext,ciphertext_len), + hexstr(decrypted,strlen(MESSAGE)), + ciphertext_len); + +} + +static void print_seal_box_vectors(void) +{ + unsigned char + recipient_pk[crypto_box_PUBLICKEYBYTES], + recipient_sk[crypto_box_SECRETKEYBYTES], + *decrypted, + *ciphertext = NULL; + + int + rc, + len, + ciphertext_len; + + rc = crypto_box_keypair(recipient_pk, recipient_sk); + assert(rc == 0); + + ciphertext_len = (crypto_box_SEALBYTES + strlen(MESSAGE)) * sizeof(unsigned char); + ciphertext = (unsigned char *) malloc(ciphertext_len); + assert(ciphertext != NULL); + + /* + ** Anonymous sender encrypts a message using an ephemeral key pair + ** and the recipient's public key + */ + rc = crypto_box_seal(ciphertext, + MESSAGE, strlen(MESSAGE), + recipient_pk); + assert(rc == 0); + + /* Recipient decrypts the ciphertext */ + decrypted = (unsigned char *) malloc(strlen(MESSAGE) * sizeof(unsigned char)); + assert(decrypted != NULL); + rc = crypto_box_seal_open(decrypted, + ciphertext, ciphertext_len, + recipient_pk, recipient_sk); + assert(rc == 0); + + /* ciphertext will be different each time */ + printf( +"\n" +"// Public-key authenticated encryption\n" +" public static final String SEALBOX_RECIPIENT_PUBLIC_KEY=\"%s\";\n" +" public static final String SEALBOX_RECIPIENT_PRIVATE_KEY=\"%s\";\n" +" public static final int SEALBOX_RECIPIENT_PUBLIC_KEY_LEN=%d;\n" +" public static final int SEALBOX_RECIPIENT_PRIVATE_KEY_LEN=%d;\n" +" public static final String SEALBOX_CIPHERTEXT=\"%s\";\n" +" public static final String SEALBOX_DECRYPTEDTEXT=\"%s\";\n" +" public static final int SEALBOX_CIPHERTEXT_LEN=%d;\n", + hexstr(recipient_pk,sizeof(recipient_pk)), + hexstr(recipient_sk,sizeof(recipient_sk)), + sizeof(recipient_pk), + sizeof(recipient_sk), + hexstr(ciphertext,ciphertext_len), + hexstr(decrypted,strlen(MESSAGE)), + ciphertext_len); + + +} + +static void print_generic_hash_vectors(void) +{ + unsigned char + hash[crypto_generichash_BYTES]; + + int + rc; + + rc = crypto_generichash(hash, sizeof(hash), + MESSAGE, strlen(MESSAGE), + NULL, 0); + assert(rc == 0); +} + + +int main(int argc,char *argv[]) +{ + if (sodium_init() == -1) + { + return(1); + } + + print_header(); + print_pwhash_argon2_vectors(); + print_pwhash_argon2_password_storage_vectors(); + print_pwhash_scrypt_vectors(); + print_secret_key_vectors(); + print_secret_key_auth_vectors(); + print_public_key_vectors(); + print_seal_box_vectors(); + print_generic_hash_vectors(); + print_footer(); + + (void) fflush(stdout); + +ExitProcessing: + return(0); +} diff --git a/pom.xml b/pom.xml new file mode 100755 index 0000000..8bbfa8a --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + com.muquit.libsodiumjna + libsodium-jna + 1.0.1 + com.muquit.libsodiumjna + Java bindings for libsodium with Java Native Access (JNA) + jar + + + UTF-8 + + 1.7 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + ${java.version} + ${java.version} + + + + + + + + + + net.java.dev.jna + jna + 4.2.2 + + + + org.slf4j + slf4j-api + 1.7.21 + + + + org.slf4j + slf4j-log4j12 + 1.7.21 + + + + + + commons-codec + commons-codec + 1.10 + + + junit + junit + 4.11 + test + + + + + + diff --git a/src/main/java/com/muquit/libsodiumjna/SodiumKeyPair.java b/src/main/java/com/muquit/libsodiumjna/SodiumKeyPair.java new file mode 100755 index 0000000..8966af7 --- /dev/null +++ b/src/main/java/com/muquit/libsodiumjna/SodiumKeyPair.java @@ -0,0 +1,40 @@ +package com.muquit.libsodiumjna; + +/** + * A class just to hold public and private keys. The crypto code is in + * SodidumLibary class + * @author muquit@muquit.com - Oct 21, 2016, 11:35:44 AM - first cut + */ +public class SodiumKeyPair +{ + private byte[] publicKey; + private byte[] privateKey; + + public SodiumKeyPair() {} + + public SodiumKeyPair(byte[] publicKey, byte[] privatekey) + { + setPublicKey(publicKey); + setPrivateKey(privatekey); + } + + public byte[] getPublicKey() + { + return this.publicKey; + } + + public byte[] getPrivateKey() + { + return this.privateKey; + } + + public void setPublicKey(byte[] publicKey) + { + this.publicKey = publicKey; + } + + public void setPrivateKey(byte[] privateKey) + { + this.privateKey = privateKey; + } +} diff --git a/src/main/java/com/muquit/libsodiumjna/SodiumLibrary.java b/src/main/java/com/muquit/libsodiumjna/SodiumLibrary.java new file mode 100755 index 0000000..c40adf1 --- /dev/null +++ b/src/main/java/com/muquit/libsodiumjna/SodiumLibrary.java @@ -0,0 +1,894 @@ +package com.muquit.libsodiumjna; + +import static com.muquit.libsodiumjna.SodiumLibrary.sodium; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.omg.PortableInterceptor.USER_EXCEPTION; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.muquit.libsodiumjna.exceptions.SodiumLibraryException; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.NativeLong; +import com.sun.jna.Pointer; + +/** + * The class that holds the binding to libsodium C API. All the methods are + * static methods. Most methods throw SodiumLibraryException at run time + * in case of errors. + * + * @author muquit@muquit.com - Oct 21, 2016, 11:37:24 AM - first cut + * + */ +public class SodiumLibrary +{ + private final static Logger logger = LoggerFactory.getLogger(SodiumLibrary.class); + + private static String libPath; + + private SodiumLibrary(){} + + public static void log(String msg) + { + System.out.println("MMMM: " + msg); + } + + /** + * Set the absolute path of the libsodium shared library/DLL. This method + * must be called before calling any methods. Although JNA supports loading + * a shared library from path, libsodium-jna requires specifying the absolute + * path to make sure that the exact library is being loaded. + * + * @param libraryPath The absolute path of the libsodium library. For example, + * in Linux, it might be /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 + *
+ * 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 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 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