Skip to main content

Cipher - Curve25519 Guideline

Effective from December 2023, we have transitioned to using the Curve25519 algorithm. Therefore, all preceding on-chain PII exchanges must be modified, moving away from the legacy RSA encryption and adopting the Curve25519 algorithm instead.

For guidance on the implementation of the Curve25519 cipher, please refer to the examples provided below.

Package Information

  • Package Name: com.goterl.lazysodium-java
  • version: 5.1.4

Example 1 - Encryption Utils

class Curve25519Utils {

private static final LazySodiumJava sodium;

static {
sodium = new LazySodiumJava(new SodiumJava(), new UrlSafeBase64MessageEncoder());
sodium.sodiumInit();
}

public static class UrlSafeBase64MessageEncoder implements MessageEncoder {

@Override
public String encode(byte[] cipher) {
return Base64.getEncoder().encodeToString(cipher);
}

@Override
public byte[] decode(String cipherText) {
return Base64.getDecoder().decode(cipherText);
}
}

public static KeyPair generateEdKeyPair() {
try {
Key seed = sodium.keygen(Method.XCHACHA20_POLY1305_IETF);
return sodium.cryptoSignSeedKeypair(seed.getAsBytes());
} catch (Exception e) {
return null;
}
}

public static byte[] decrypt(byte[] data, byte[] sharedKey) throws SodiumException {
byte[] nonce = Arrays.copyOfRange(data, 0, Box.NONCEBYTES);
byte[] encryptedMsg = Arrays.copyOfRange(data, Box.NONCEBYTES, data.length);
byte[] decrypted = new byte[encryptedMsg.length - Box.MACBYTES];
if (!sodium.cryptoBoxOpenEasyAfterNm(
decrypted, encryptedMsg, encryptedMsg.length, nonce, sharedKey)) {
throw new SodiumException("Could not decrypt data");
}
return decrypted;
}

public static String decrypt(String data, String selfPrivateKey, String remotePublicKey)
throws SodiumException {
byte[] dataBytes = Base64.getDecoder().decode(data);
byte[] sharedKey = getSharedKey(remotePublicKey, selfPrivateKey);
return new String(decrypt(dataBytes, sharedKey));
}

private static byte[] getSharedKey(byte[] publicKey, byte[] privateKey) {
byte[] sharedKey = new byte[Box.BEFORENMBYTES];
byte[] curPublicKey = new byte[Sign.CURVE25519_PUBLICKEYBYTES];
byte[] curPrivateKey = new byte[Sign.CURVE25519_SECRETKEYBYTES];
sodium.convertPublicKeyEd25519ToCurve25519(curPublicKey, publicKey);
sodium.convertSecretKeyEd25519ToCurve25519(curPrivateKey, privateKey);
sodium.cryptoBoxBeforeNm(sharedKey, curPublicKey, curPrivateKey);
return sharedKey;
}

private static byte[] getSharedKey(String publicKey, String privateKey) {
byte[] pukBytes = Base64.getDecoder().decode(publicKey);
byte[] prkBytes = Base64.getDecoder().decode(privateKey);
return getSharedKey(pukBytes, prkBytes);
}

public static byte[] encrypt(byte[] data, byte[] sharedKey) throws SodiumException, IOException {
byte[] nonce = sodium.randomBytesBuf(Box.NONCEBYTES);
byte[] encrypted = new byte[data.length + Box.MACBYTES];
if (!sodium.cryptoBoxEasyAfterNm(encrypted, data, data.length, nonce, sharedKey)) {
throw new SodiumException("Could not encrypt data");
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(nonce);
out.write(encrypted);
return out.toByteArray();
}

public static String encrypt(String data, String selfPrivateKey, String remotePublicKey)
throws SodiumException, IOException {
byte[] dataBytes = data.getBytes();
byte[] sharedKey = getSharedKey(remotePublicKey, selfPrivateKey);
return Base64.getEncoder().encodeToString(encrypt(dataBytes, sharedKey));
}
}

Example 2 - Generate Public / Private Key Pair and Encryption

KeyPair aliceKeyPair = Curve25519Utils.generateEdKeyPair();
String alicePubKey = new String(Base64.getEncoder().encode(aliceKeyPair.getPublicKey().getAsBytes()));
System.out.println(alicePubKey);
String alicePrivateKey = new String(Base64.getEncoder().encode(aliceKeyPair.getSecretKey().getAsBytes()));
System.out.println(alicePrivateKey);


KeyPair bobKeyPair = Curve25519Utils.generateEdKeyPair();
String bobPubKey = new String(Base64.getEncoder().encode(bobKeyPair.getPublicKey().getAsBytes()));
System.out.println(bobPubKey);
String bobPrivateKey = new String(Base64.getEncoder().encode(bobKeyPair.getSecretKey().getAsBytes()));
System.out.println(bobPrivateKey);


// alice encrypt message
String result = Curve25519Utils.encrypt("Message...", alicePrivateKey, bobPubKey);


// bob decrypt message
String decrypt = Curve25519Utils.decrypt(result, bobPrivateKey, alicePubKey);

// decrypt
System.out.println(decrypt);

Example 3 - Golang Encryption Example

package main

import (
"crypto/rand"
"encoding/base64"
"fmt"

"github.com/agl/ed25519/extra25519" // go get github.com/agl/ed25519@v0.0.0-20170116200512-5312a6153412
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/nacl/box"
)

type Curve25519 struct {
PrivateKey *[32]byte
PublicKey *[32]byte
}

func NewCurve25519(base64PrivateKey string, base64PublicKey string) (*Curve25519, error) {
privBytes, _ := base64.StdEncoding.DecodeString(base64PrivateKey)
pubBytes, _ := base64.StdEncoding.DecodeString(base64PublicKey)

var privateKey [32]byte
var publicKey [32]byte

copy(privateKey[:], privBytes)
copy(publicKey[:], pubBytes)

return &Curve25519{PrivateKey: &privateKey, PublicKey: &publicKey}, nil
}

func (cc *Curve25519) Encrypt(msg string) ([]byte, error) {
var nonce [24]byte
_, err := rand.Read(nonce[:])
if err != nil {
return nil, err
}

encrypted := box.Seal(nonce[:], []byte(msg), &nonce, cc.PublicKey, cc.PrivateKey)
return encrypted, nil
}

func (cc *Curve25519) Decrypt(encrypted []byte) ([]byte, error) {
var Nonce [24]byte
copy(Nonce[:], encrypted[:24])

decrypted, success := box.Open(nil, encrypted[24:], &Nonce, cc.PublicKey, cc.PrivateKey)
if !success {
return nil, fmt.Errorf("Failed to decrypt message")
}

return decrypted, nil
}

func main() {

// Generate key pair for sender and receiver
senderPublicKey, senderPrivateKey, _ := ed25519.GenerateKey(rand.Reader)
receiverPublicKey, receiverPrivateKey, _ := ed25519.GenerateKey(rand.Reader)

// Convert keys to base64 for easier representation
base64SenderPrivateKey := base64.StdEncoding.EncodeToString(senderPrivateKey.Seed()[:])
base64SenderPublicKey := base64.StdEncoding.EncodeToString(senderPublicKey[:])
base64ReceiverPrivateKey := base64.StdEncoding.EncodeToString(receiverPrivateKey.Seed()[:])
base64ReceiverPublicKey := base64.StdEncoding.EncodeToString(receiverPublicKey[:])

fmt.Println("Please provide the following formats key to GTR")
fmt.Println("=====================")
fmt.Println("Sender's Private Key (Base64 Encoded): ", base64SenderPrivateKey)
fmt.Println("Sender's Public Key (Base64 Encoded): ", base64SenderPublicKey)
fmt.Println("Receiver's Public Key (Base64 Encoded): ", base64ReceiverPublicKey)
fmt.Println("Receiver's Private Key (Base64 Encoded): ", base64ReceiverPrivateKey)
fmt.Println("=====================\n\n")

fmt.Println("The following curve25519 converted key is not support by GTR, please do not use it, this is in golang encryption only")
fmt.Println("=====================")

var curve25519SenderPrivateKey [32]byte
// Convert the ed25519 private key to a curve25519 private key
// We ignore the first 32 bytes of the key, as specified by extra25519's function
extra25519.PrivateKeyToCurve25519(&curve25519SenderPrivateKey, (*[64]byte)(senderPrivateKey))
fmt.Printf("1. Curve25519 Private Key: %v\n", base64.StdEncoding.EncodeToString(curve25519SenderPrivateKey[:]))

var curve25519ReceiverPublicKey [32]byte
extra25519.PublicKeyToCurve25519(&curve25519ReceiverPublicKey, (*[32]byte)(receiverPublicKey))
fmt.Printf("1. Curve25519 Public Key: %v\n", base64.StdEncoding.EncodeToString(curve25519ReceiverPublicKey[:]))

//
senderCC, err := NewCurve25519(base64.StdEncoding.EncodeToString(curve25519SenderPrivateKey[:]), base64.StdEncoding.EncodeToString(curve25519ReceiverPublicKey[:]))
if err != nil {
panic(err)
}
fmt.Println("=====================\n\n")

fmt.Println("Please provide the following payload to GTR as encryptedPayload")
fmt.Println("=====================")
// Sender encrypts the message
encryptedMessage, err := senderCC.Encrypt("{\"ivms101\":{}}")
if err != nil {
panic(err)
}

fmt.Println("Encrypted Message (Base64 Encoded): ", base64.StdEncoding.EncodeToString(encryptedMessage))
fmt.Println("=====================\n\n")

fmt.Println("The following curve25519 converted key is not support by GTR, please do not use it, this is in golang encryption only")
fmt.Println("=====================")
var curve25519ReceiverPrivateKey2 [32]byte
// Convert the ed25519 private key to a curve25519 private key
// We ignore the first 32 bytes of the key, as specified by extra25519's function
extra25519.PrivateKeyToCurve25519(&curve25519ReceiverPrivateKey2, (*[64]byte)(receiverPrivateKey))
fmt.Printf("2. Curve25519 Private Key: %v\n", base64.StdEncoding.EncodeToString(curve25519ReceiverPrivateKey2[:]))

var curve25519SenderPublicKey2 [32]byte
extra25519.PublicKeyToCurve25519(&curve25519SenderPublicKey2, (*[32]byte)(senderPublicKey))
fmt.Printf("2. Curve25519 Public Key: %v\n", base64.StdEncoding.EncodeToString(curve25519SenderPublicKey2[:]))

// Initialize receiver's crypto struct
receiverCC, err := NewCurve25519(base64.StdEncoding.EncodeToString(curve25519ReceiverPrivateKey2[:]), base64.StdEncoding.EncodeToString(curve25519SenderPublicKey2[:]))
if err != nil {
panic(err)
}
fmt.Println("=====================\n")

// Receiver decrypts the message
decryptedMessage, err := receiverCC.Decrypt(encryptedMessage)
if err != nil {
panic(err)
}
fmt.Println("Decrypted Message: ", string(decryptedMessage))
}

Example 4 - Golang Load String Key to Encrypt

Some of scenario, you may directly use the string key (base64) load into the variable and encrypt the payload, the above example 3 are using curve conversion that need to use 64 byte private key, but the above example gives the trim general 64 byte ed25519 private key to 32 byte base64, so we have to pad the zero to the 64 byte key to tail, here is the example for loading the key from the string.

func paddingShift(bb []byte, size int) []byte {
l := len(bb)
if l == size {
return bb
}
if l > size {
return bb[l-size:]
}
tmp := make([]byte, size)
copy(tmp[:size-l], bb)
return tmp
}

func main() {
senderPrivateKey, _ := base64.StdEncoding.DecodeString("YOUR BASE64 PRIVATE KEY HERE")
senderPrivateKey = paddingShift(senderPrivateKey, 64)
receiverPublicKey, _ := base64.StdEncoding.DecodeString("YOUR BASE64 PUBLIC KEY HERE")

var curve25519SenderPrivateKey [32]byte
// Convert the ed25519 private key to a curve25519 private key
// We ignore the first 32 bytes of the key, as specified by extra25519's function
extra25519.PrivateKeyToCurve25519(&curve25519SenderPrivateKey, (*[64]byte)(senderPrivateKey))

var curve25519ReceiverPublicKey [32]byte
extra25519.PublicKeyToCurve25519(&curve25519ReceiverPublicKey, (*[32]byte)(receiverPublicKey))
senderCC, err := NewCurve25519(base64.StdEncoding.EncodeToString(curve25519SenderPrivateKey[:]), base64.StdEncoding.EncodeToString(curve25519ReceiverPublicKey[:]))
if err != nil {
panic(err)
}
encryptedMessage, err := senderCC.Encrypt("{\"ivms101\":{}}")
if err != nil {
panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(encryptedMessage))
}

Example 5 - Python NaCl Example

import nacl.hash
from nacl.encoding import Base64Encoder
from nacl.public import Box
from nacl.signing import SigningKey, VerifyKey
from base64 import b64encode, b64decode

# Sender
sender_signing_key = SigningKey.generate()
sender_verify_key = sender_signing_key.verify_key
sender_private_key = b64encode(bytes(sender_signing_key)).decode('utf-8')
sender_public_key = b64encode(bytes(sender_verify_key)).decode('utf-8')
print("Sender Private Key: ", sender_private_key)
print("Sender Public Key: ", sender_public_key)

curve25519_sender_private_key = SigningKey(sender_private_key.encode('utf-8'),encoder=nacl.encoding.Base64Encoder).to_curve25519_private_key()
curve25519_sender_public_key = VerifyKey(sender_public_key.encode('utf-8'),encoder=nacl.encoding.Base64Encoder).to_curve25519_public_key()

# Receiver
receiver_signing_key = SigningKey.generate()
receiver_verify_key = receiver_signing_key.verify_key
receiver_private_key = b64encode(bytes(receiver_signing_key)).decode('utf-8')
receiver_public_key = b64encode(bytes(receiver_verify_key)).decode('utf-8')
print("Receiver Private Key: ", receiver_private_key)
print("Receiver Public Key: ", receiver_public_key)

curve25519_receiver_private_key = SigningKey(receiver_private_key.encode('utf-8'),encoder=nacl.encoding.Base64Encoder).to_curve25519_private_key()
curve25519_receiver_public_key = VerifyKey(receiver_public_key.encode('utf-8'),encoder=nacl.encoding.Base64Encoder).to_curve25519_public_key()

# Sender Encryption
sender_encrypt_box = Box(curve25519_sender_private_key, curve25519_receiver_public_key)

encrypted_message = sender_encrypt_box.encrypt(bytes("{\"ivms101\":{}}", "utf-8"))
b64encrypted_message = b64encode(encrypted_message).decode('utf-8')
print("Sender encrypted payload: ", b64encrypted_message)
print("============================")
receiver_decrypt_box = Box(curve25519_receiver_private_key, curve25519_sender_public_key)

decrypted_message = receiver_decrypt_box.decrypt(b64decode(b64encrypted_message)).decode('utf-8')
print("Receiver decrypted payload: ",decrypted_message)
Copyright (C) 2024 Global Travel Rule. All Rights Reserved
General
Developer