Skip to content
This repository has been archived by the owner on Sep 26, 2023. It is now read-only.

Commit

Permalink
Fix Curve25519
Browse files Browse the repository at this point in the history
  • Loading branch information
M3DZIK committed Jun 10, 2023
1 parent 9bd7b60 commit 3ca89c6
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 122 deletions.
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@
<version>1.7.1</version>
</dependency>

<!-- Curve25519 algorithm implementation -->
<!-- Google tink -->
<dependency>
<groupId>org.whispersystems</groupId>
<artifactId>curve25519-java</artifactId>
<version>0.5.0</version>
<groupId>com.google.crypto.tink</groupId>
<artifactId>tink</artifactId>
<version>1.9.0</version>
</dependency>

<!-- Tests -->
Expand Down
85 changes: 22 additions & 63 deletions src/main/java/dev/medzik/libcrypto/Curve25519.java
Original file line number Diff line number Diff line change
@@ -1,88 +1,47 @@
package dev.medzik.libcrypto;

import com.google.crypto.tink.subtle.X25519;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.security.InvalidKeyException;

/**
* Curve25519 implementation. This class is a wrapper around the WhisperSystems Curve25519 implementation
* with a Hex encoding/decoding layer.
* <a href="https://en.wikipedia.org/wiki/Curve25519">See Curve25519 on Wikipedia</a>
* Curve25519 implementation using Google Tink.
*/
public class Curve25519 {
private static final org.whispersystems.curve25519.Curve25519 curve25519 = org.whispersystems.curve25519.Curve25519.getInstance(org.whispersystems.curve25519.Curve25519.JAVA);
/**
* Generate a new X25519 key pair.
* @return X25519 key pair.
*/
public static Curve25519KeyPair generateKeyPair() throws InvalidKeyException, DecoderException {
byte[] privateKey = X25519.generatePrivateKey();
return fromPrivateKey(Hex.encodeHexString(privateKey));
}

/**
* Generate a new Curve25519 key pair.
* @return Curve25519 key pair.
* Return a X25519 key pair from a private key.
* @param privateKey private key to recover (hex encoded)
* @return X25519 key pair.
*/
public static Curve25519KeyPair generateKeyPair() {
org.whispersystems.curve25519.Curve25519KeyPair keyPair = curve25519.generateKeyPair();
return new Curve25519KeyPair(keyPair.getPublicKey(), keyPair.getPrivateKey());
public static Curve25519KeyPair fromPrivateKey(String privateKey) throws DecoderException, InvalidKeyException {
byte[] privateKeyBytes = Hex.decodeHex(privateKey);
byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes);

return new Curve25519KeyPair(publicKeyBytes, publicKeyBytes);
}

/**
* Calculate a shared secret given our private key and their public key.
* Compute a shared secret given our private key and their public key.
* @param ourPrivate our private key
* @param theirPublic their public key
* @return Shared secret.
*/
public static String calculateAgreement(String ourPrivate, String theirPublic) throws DecoderException {
public static String computeSharedSecret(String ourPrivate, String theirPublic) throws DecoderException, InvalidKeyException {
byte[] outPrivateBytes = Hex.decodeHex(ourPrivate);
byte[] theirPublicBytes = Hex.decodeHex(theirPublic);

byte[] sharedSecret = curve25519.calculateAgreement(outPrivateBytes, theirPublicBytes);
byte[] sharedSecret = X25519.computeSharedSecret(outPrivateBytes, theirPublicBytes);

return Hex.encodeHexString(sharedSecret);
}

/**
* Calculate a Curve25519 signature given a private key and a message.
* @param privateKey private key to signing
* @param message message to sign (hex encoded)
* @return Curve25519 signature.
*/
public static String calculateSignature(String privateKey, String message) throws DecoderException {
byte[] privateKeyBytes = Hex.decodeHex(privateKey);
byte[] messageBytes = Hex.decodeHex(message);

byte[] signature = curve25519.calculateSignature(privateKeyBytes, messageBytes);

return Hex.encodeHexString(signature);
}

/**
* Calculate a Curve25519 signature given a private key and a message.
* @param privateKey private key to signing
* @param message message to sign
* @return Curve25519 signature.
*/
public static String calculateSignature(String privateKey, byte[] message) throws DecoderException {
return calculateSignature(privateKey, Hex.encodeHexString(message));
}

/**
* Verify a Curve25519 signature given a public key, message, and signature.
* @param publicKey public key to verify
* @param message message to verify (hex encoded)
* @param signature signature to verify
* @return True if the signature is valid, false otherwise.
*/
public static boolean verifySignature(String publicKey, String message, String signature) throws DecoderException {
byte[] publicKeyBytes = Hex.decodeHex(publicKey);
byte[] messageBytes = Hex.decodeHex(message);
byte[] signatureBytes = Hex.decodeHex(signature);

return curve25519.verifySignature(publicKeyBytes, messageBytes, signatureBytes);
}

/**
* Verify a Curve25519 signature given a public key, message, and signature.
* @param publicKey public key to verify
* @param message message to verify
* @param signature signature to verify
* @return True if the signature is valid, false otherwise.
*/
public static boolean verifySignature(String publicKey, byte[] message, String signature) throws DecoderException {
return verifySignature(publicKey, Hex.encodeHexString(message), signature);
}
}
4 changes: 2 additions & 2 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// Password4j (used for argon2 implementation)
requires password4j;

// Curve25519 implementation
requires curve25519.java;
// Google Tink (used for Curve25519 implementation)
requires com.google.crypto.tink;

exports dev.medzik.libcrypto;
}
72 changes: 19 additions & 53 deletions src/test/java/dev/medzik/libcrypto/Curve25519Tests.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,93 +3,59 @@
import org.apache.commons.codec.DecoderException;
import org.junit.jupiter.api.Test;

import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;

public class Curve25519Tests {
@Test
public void testGenerateKeyPair() {
public void testGenerateKeyPair() throws DecoderException, InvalidKeyException {
Curve25519KeyPair keyPair = Curve25519.generateKeyPair();

assert keyPair.getPrivateKey().length() == 64;
assert keyPair.getPublicKey().length() == 64;
}

@Test
public void testCalculateAgreement() throws DecoderException {
public void textComputeSharedSecret() throws DecoderException, InvalidKeyException {
Curve25519KeyPair our = Curve25519.generateKeyPair();
Curve25519KeyPair their = Curve25519.generateKeyPair();

String ourPrivate = our.getPrivateKey();
String theirPublic = their.getPublicKey();

String sharedSecret = Curve25519.calculateAgreement(ourPrivate, theirPublic);
String sharedSecret = Curve25519.computeSharedSecret(ourPrivate, theirPublic);

assert sharedSecret.length() == 64;
}

@Test
public void testCalculateAgreementEncrypt() throws DecoderException, EncryptException {
public void textComputeSharedSecret2() throws DecoderException, InvalidKeyException {
String privateKey = "3845bead1f44408ee436c742291f1362489eeaaa9daebd480b1c3e4bc528cb48";
String publicKey = "9d49b72cf49defc6748c67ab274a1c2f096362ef3b2d691793686589760b4e25";

String sharedSecret = Curve25519.computeSharedSecret(privateKey, publicKey);

assert sharedSecret.equals("2bebf3c397ab3c79db9aeeb2c1523ab4a32bd1ae335a19cd47e35983a5184d09");
}

@Test
public void testCalculateAgreementEncrypt() throws DecoderException, EncryptException, InvalidKeyException {
Curve25519KeyPair keyPair = Curve25519.generateKeyPair();

String ourPrivate = keyPair.getPrivateKey();
String theirPublic = keyPair.getPublicKey();

String sharedSecret = Curve25519.calculateAgreement(ourPrivate, theirPublic);
String sharedSecret = Curve25519.computeSharedSecret(ourPrivate, theirPublic);

String cipherText = AES.encrypt(AES.GCM, sharedSecret, "Hello, world!");

String theirPrivate = keyPair.getPrivateKey();
String outPublic = keyPair.getPublicKey();

String sharedSecretTwo = Curve25519.calculateAgreement(theirPrivate, outPublic);
String sharedSecretTwo = Curve25519.computeSharedSecret(theirPrivate, outPublic);

String plainText = AES.decrypt(AES.GCM, sharedSecretTwo, cipherText);

assert plainText.equals("Hello, world!");
}

@Test
public void testCalculateSignature() throws DecoderException {
Curve25519KeyPair keyPair = Curve25519.generateKeyPair();

String privateKey = keyPair.getPrivateKey();
byte[] message = "Hello, world!".getBytes();

String signature = Curve25519.calculateSignature(privateKey, message);

assert signature.length() == 128;
}

@Test
public void testVerifySignature() throws DecoderException {
Curve25519KeyPair keyPair = Curve25519.generateKeyPair();

String publicKey = keyPair.getPublicKey();
byte[] message = "Hello, world!".getBytes();
String signature = Curve25519.calculateSignature(keyPair.getPrivateKey(), message);

assert Curve25519.verifySignature(publicKey, message, signature);
}

@Test
public void testVerifySignatureInvalidSignature() throws DecoderException {
Curve25519KeyPair keyPair = Curve25519.generateKeyPair();

String publicKey = keyPair.getPublicKey();
byte[] message = "Hello, world!".getBytes();
String signature = Curve25519.calculateSignature(keyPair.getPrivateKey(), message);

signature = signature.substring(1, signature.length() - 1);

assert !Curve25519.verifySignature(publicKey, message, signature);
}

@Test
public void testVerifySignatureInvalidMessage() throws DecoderException {
Curve25519KeyPair keyPair = Curve25519.generateKeyPair();

String publicKey = keyPair.getPublicKey();
byte[] message = "Hello, world!".getBytes();
String signature = Curve25519.calculateSignature(keyPair.getPrivateKey(), message);

assert !Curve25519.verifySignature(publicKey, "Goodbye, world!".getBytes(), signature);
}
}

0 comments on commit 3ca89c6

Please sign in to comment.