简体   繁体   中英

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 .

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:

  • First, a key pair is needed on the JavaScript side, whose public key ( publicKeyJS ) is passed to the Java side. The key pair on the JavaScript side can be generated as follows:

     let keysJS = nacl.box.keyPair(); let secretKeyJS = keysJS.secretKey; let publicKeyJS = keysJS.publicKey; console.log("Secret key: " + nacl.util.encodeBase64(secretKeyJS)); console.log("Public key: " + nacl.util.encodeBase64(publicKeyJS));

    with the following sample output:

     Secret key: YTxAFmYGm4yV2OP94E4pcD6LSsN4gcSBBAlU105l7hw= Public key: BDXNKDHeq0vILm8oawAGAQtdIsgwethzBTBqmsWI+R8=
  • The encryption on the Java side is then using the encrypt method posted above (and publicKeyJS ):

     byte[] publicKeyJS = Base64.getDecoder().decode("BDXNKDHeq0vILm8oawAGAQtdIsgwethzBTBqmsWI+R8="); EncryptedData encryptedFromJava = encrypt(publicKeyJS, "I've got a feeling we're not in Kansas anymore..."); System.out.println("Nonce: " + encryptedFromJava.nonce); System.out.println("Ephemeral public key: " + encryptedFromJava.ephemPubKey); System.out.println("Ciphertext: " + encryptedFromJava.encrypted);

    with the following sample output:

     Nonce: FcdzXfYwSbI0nq2WXsLe9aAh94vXSoWd Ephemeral public key: Mde+9metwF1jIEij5rlZDHjAStR/pd4BN9p5JbZleSg= Ciphertext: hHo7caCxTU+hghcFZFv+djAkSlWKnC12xj82V2R/Iz9GdOMoTzjoCDcz9m/KbRN6i5dkYi3+Gf0YTtKlZQWFooo=
  • The decryption on the JS side gives the original plaintext (using secretKeyJS ):

     let nonce = "FcdzXfYwSbI0nq2WXsLe9aAh94vXSoWd"; let ephemPubKey = "Mde+9metwF1jIEij5rlZDHjAStR/pd4BN9p5JbZleSg="; let encrypted = "hHo7caCxTU+hghcFZFv+djAkSlWKnC12xj82V2R/Iz9GdOMoTzjoCDcz9m/KbRN6i5dkYi3+Gf0YTtKlZQWFooo="; let secretKeyJS = nacl.util.decodeBase64("YTxAFmYGm4yV2OP94E4pcD6LSsN4gcSBBAlU105l7hw="); let decryptedFromJS = decrypt(secretKeyJS, {nonce: nonce, ephemPubKey: ephemPubKey, encrypted: encrypted}); console.log(nacl.util.encodeUTF8(decryptedFromJS)); // I've got a feeling we're not in Kansas anymore... function decrypt(secretKey, ciphertext){ let decrypted = nacl.box.open( nacl.util.decodeBase64(ciphertext.encrypted), nacl.util.decodeBase64(ciphertext.nonce), nacl.util.decodeBase64(ciphertext.ephemPubKey), secretKey ); return decrypted; }
     <script src="https://cdn.jsdelivr.net/npm/tweetnacl-util@0.15.1/nacl-util.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script>

My complete code for tweetnacl-java (Kudos to @topaco)

I generated two random key-pairs and saved their secret keys in the application.properties file, so that, i will always have the same pub&sec along with the nonce.

KeyPair baseKeyPair= Box.keyPair(); String baseKeyPairSecretKey = Base64.getEncoder().encodeToString(baseKeyPair.getSecretKey());

KeyPair ephemeralKeyPair= Box.keyPair(); String ephemeralKeyPairSecretKey = Base64.getEncoder().encodeToString(ephemeralKeyPair.getSecretKey());

byte[] nonce = TweetNaclFast.randombytes(Box.nonceLength); String nonce64 = Base64.getEncoder().encodeToString(nonce);

 private final AppConfig config; //you can autowire the config class 

 private TweetNaclFast.Box.KeyPair getBaseKeyPair() {
        byte[] secretKey = Base64.getDecoder().decode(config.getTweetNACLConfig().getBaseSecretKey());
        return TweetNaclFast.Box.keyPair_fromSecretKey(mySecretKey);
    }


    private TweetNaclFast.Box.KeyPair getEphemeralKeyPair() {
        byte[] secretKey = Base64.getDecoder().decode(config.getTweetNACLConfig().getEphemeralSecretKey());
        return TweetNaclFast.Box.keyPair_fromSecretKey(mySecretKey);
    }


    private byte[] getNonce() {
        return Base64.getDecoder().decode(config.getTweetNACLConfig().getNonce().getBytes(StandardCharsets.UTF_8));
    }

    public String encrypt(String msg) {
        TweetNaclFast.Box.KeyPair baseKeyPair = getBaseKeyPair();
        TweetNaclFast.Box.KeyPair ephemeralKeyPair = getEphemeralKeyPair();
        byte[] msgArr = msg.getBytes(StandardCharsets.UTF_8);
        byte[] nonce = getNonce();
        TweetNaclFast.Box box = new TweetNaclFast.Box(baseKeyPair.getPublicKey(), ephemeralKeyPair.getSecretKey());
        byte[] encryptedData = box.box(msgArr, nonce);
        return Base64.getEncoder().encodeToString(encryptData);
    }


    public String decrypt(String encryptedData) {
        TweetNaclFast.Box.KeyPair baseKeyPair = getBaseKeyPair();
        TweetNaclFast.Box.KeyPair ephemeralKeyPair = getEphemeralKeyPair();
        byte[] nonce = getNonce();
        TweetNaclFast.Box box = new TweetNaclFast.Box(ephemeralKeyPair.getPublicKey(), baseKeyPair.getSecretKey());
        byte[] boxToOpen = Base64.getDecoder().decode(encryptedData);
        byte[] decryptedData = box.open(boxToOpen, nonce);
        return new String(decryptedData, StandardCharsets.UTF_8);
    }

> Please, note these two lines
> TweetNaclFast.Box box = new TweetNaclFast.Box(baseKeyPair.getPublicKey(), ephemeralKeyPair.getSecretKey());
> TweetNaclFast.Box box = new TweetNaclFast.Box(ephemeralKeyPair.getPublicKey(), baseKeyPair.getSecretKey());

return encryptAndDecryptData.encrypt("Friday"); // JHo/tk/Jpp2rpxpzIIgBhVhK/CBZLg==
return encryptAndDecryptData.decrypt("JHo/tk/Jpp2rpxpzIIgBhVhK/CBZLg==") //Friday

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM