pythonencryptionpynacltweet-nacl

Encrypt in tweet-nacl (javascript) and decrypt in python


This question is the inverse of the existing one here: Encrypt in python 3.7 and decode in NODEJS 12 .

I would prefer to use the exact equivalent of tweet-nacl on python but that project says it is old and not recommended https://github.com/warner/python-tweetnacl . Their recommended replacement is https://github.com/pyca/pynacl : but that one is an interface to libsodium not tweet-nacl and there is no clear documentation on how to achieve the decryption.

Here is the JS encryption:

    let msgArr = naclutil.decodeUTF8(jprint(msg))
    let nonce = nacl.randomBytes(nacl.box.nonceLength)
    let keyPair = this.genKeyPair()
    let encrypted = nacl.box(
      msgArr,
      nonce,
      naclutil.decodeBase64(pubKey),
      naclutil.decodeBase64(keyPair.privkey)
    )
    let nonce64 = naclutil.encodeBase64(nonce)
    let encrypted64 = naclutil.encodeBase64(encrypted)

The (working) tweet-nacl javascript decryption code is:

  const decryptedMessage = nacl.box.open(
    naclutil.decodeBase64(payload.encrypted.encrypted),
    naclutil.decodeBase64(payload.encrypted.nonce),
    naclutil.decodeBase64(payload.encrypted.ephemPubKey),
    naclutil.decodeBase64(privKey)
  )
  const decodedMessage = naclutil.encodeUTF8(decryptedMessage)

My problem is that for pynacl they do not show any examples of using the ephemPubKey for decryption. The examples I could find were like the following:

        import binascii
        from nacl.encoding import HexEncoder
        from nacl.exceptions import CryptoError
        from nacl.secret import Aead, SecretBox
        benc=       binascii.unhexlify(encrypted)
        bnonce =    binascii.unhexlify(nonce)
        box = SecretBox(privKey, encoder=HexEncoder)
        decrypted = box.decrypt(benc, bnonce, encoder=HexEncoder),

Has anyone been able to get the tweet-nacl Javascript generated encryption successfully decrypted into python?


Solution

  • SecretBox and thus the PyNaCl example you posted is for symmetric encryption, s. here. You can find the documentation for public key encryption with PyNaCl here and for PyNaCl in general here.

    In the following example, a plaintext is encrypted with TweetNaCl.js and decrypted with PyNaCl.

    JavaScript side - Encryption with TweetNaCl.js:
    var secretKey_js  = nacl.util.decodeBase64("FJGsHP0dMkDNkpAkT4hZrcbv27L8XNO8ymhLxpPpDkE=");
    var publicKey_py = nacl.util.decodeBase64("0EyrzGW6qn0EGEV0Cx2Z7tQeln6FdwZVINz0FezlvTM=");
    var nonce = nacl.randomBytes(24)
    var msgStr = "The quick brown fox jumps over the lazy dog";
    var message = nacl.util.decodeUTF8(msgStr);
    var box_js = nacl.box(message, nonce, publicKey_py, secretKey_js)
    console.log(nacl.util.encodeBase64(nonce))  // 2e8WuEr0+5nc14VBxQrOl4ob6guOTySr
    console.log(nacl.util.encodeBase64(box_js)) // eJ8sO0mFNaaWLeXVcNNpw0PurwfINp/BlnErSzOnxXJ5zqu3wLrW4fHIa4kIAxFkuMVJaf0AR4pYon0=
    

    The code is basically the same as your code with the difference that the keys are not generated but imported.

    Python side - Decryption with PyNaCl:
    import base64
    from nacl.public import PrivateKey, PublicKey, Box
    from nacl.encoding import Base64Encoder
    
    secretKeyB64_py  = "XVdFnozXd+7xm6MVazPemgSq6un+fGpDvwgxo9UbsdM=";
    publicKeyB64_js = "ixxgLis2RzqMWys76HuoH7TwrwBbXoDrwl3jGsRysRI=";
    
    secretKey_py = PrivateKey(secretKeyB64_py, encoder=Base64Encoder)
    publicKey_js = PublicKey(publicKeyB64_js, encoder=Base64Encoder)
    
    nonce = base64.b64decode("2e8WuEr0+5nc14VBxQrOl4ob6guOTySr");
    box_js = base64.b64decode("eJ8sO0mFNaaWLeXVcNNpw0PurwfINp/BlnErSzOnxXJ5zqu3wLrW4fHIa4kIAxFkuMVJaf0AR4pYon0=");
    
    box_py = Box(secretKey_py, publicKey_js)
    data = box_py.decrypt(nonce + box_js)
    
    print(data) # b'The quick brown fox jumps over the lazy dog'
    

    In the example above, the keys have been imported. If a key pair needs to be generated, this is done with PyNaCl as follows:

    from nacl.public import PrivateKey
    
    secretKeyNew = PrivateKey.generate()
    publicKeyNew = secretKeyNew.public_key
    

    About compatibility:
    TweetNaCl.js and PyNaCl are compatible. As you described, although PyNaCl is a wrapper of Libsodium for Python (s. here and here), but Libsodium itself is a port of NaCl (s. here), as is TweetNacl.js for JavaScript (here).
    So ultimately, both TweetNaCl and PyNaCl are based on NaCl, the original library implemented by Bernstein et al., s. here, and are therefore compatible (except perhaps for a few minor syntactical differences).