javascriptjavaencryptionencryption-asymmetrictweet-nacl

Mixing tweetnacl.js with TweetNaclFast (java) for asymmetric encryption


Our project is using asymmetric encryption with nacl.box and ephemeral keys:

    encrypt(pubKey, msg) {
        if (typeof msg !== 'string') {
            msg = JSON.stringify(msg)
        }
        let ephemKeys = nacl.box.keyPair()
        let msgArr = nacl.util.decodeUTF8(msg)
        let nonce = nacl.randomBytes(nacl.box.nonceLength)
        p(`naclRsa.pubKey=${this.pubKey}`)
        let encrypted = nacl.box(
            msgArr,
            nonce,
            nacl.util.decodeBase64(pubKey),
            ephemKeys.secretKey
        )
        let nonce64 = nacl.util.encodeBase64(nonce)
        let pubKey64 = nacl.util.encodeBase64(ephemKeys.publicKey)
        let encrypted64 = nacl.util.encodeBase64(encrypted)
        return {nonce: nonce64, ephemPubKey: pubKey64, encrypted: encrypted64}
    }

We presently have node.js apps that then decrypt these messages. We would like the option to use jvm languages for some features. There does not seem to be the richness of established players for tweet-nacl on the jvm but it seems

and its recommended implementation

° tweetnacl-fast https://github.com/InstantWebP2P/tweetnacl-java/blob/master/src/main/java/com/iwebpp/crypto/TweetNaclFast.java

were a popular one.

It is unclear what the analog to the asymmetric encryption with ephemeral keys were in that library. Is it supported? Note that I would be open to either java or kotlin if this were not supported in tweetnacl-java.


Solution

  • tweetnacl-java is a port of tweetnacl-js. It is therefore to be expected that both provide the same functionality. At least for the posted method this is the case, which can be implemented on the Java side with TweetNaclFast as follows:

    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    
    import com.iwebpp.crypto.TweetNaclFast;
    import com.iwebpp.crypto.TweetNaclFast.Box;
    import com.iwebpp.crypto.TweetNaclFast.Box.KeyPair;
    
    ...
    
    private static EncryptedData encrypt(byte[] pubKey, String msg) {
        KeyPair ephemKeys = Box.keyPair();
        byte[] msgArr = msg.getBytes(StandardCharsets.UTF_8);
        byte[] nonce = TweetNaclFast.randombytes(Box.nonceLength);
        
        Box box = new Box(pubKey, ephemKeys.getSecretKey());
        byte[] encrypted = box.box(msgArr, nonce);
        
        String nonce64 = Base64.getEncoder().encodeToString(nonce);
        String ephemPubKey64 = Base64.getEncoder().encodeToString(ephemKeys.getPublicKey());
        String encrypted64 = Base64.getEncoder().encodeToString(encrypted);
        return new EncryptedData(nonce64, ephemPubKey64, encrypted64);
    }
    
    ...
    
    class EncryptedData {
        public EncryptedData(String nonce, String ephemPubKey, String encrypted) {
            this.nonce = nonce;
            this.ephemPubKey = ephemPubKey;
            this.encrypted = encrypted;
        }
        public String nonce;
        public String ephemPubKey;
        public String encrypted;
    }
    

    In order to demonstrate that both sides are compatible, in the following a plaintext is encrypted on the Java side and decrypted on the JavaScript side: