Skip to content

Commit

Permalink
Add ChaCha20-Poly1305 Support for OpenSSH Keys
Browse files Browse the repository at this point in the history
- Updated ChachaPolyCipher to support decryption without Additional Authenticated Data
  • Loading branch information
exceptionfactory committed Oct 16, 2023
1 parent 3b67d2b commit 7f852d9
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
package com.hierynomus.sshj.transport.cipher;

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

import java.security.MessageDigest;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.spec.IvParameterSpec;
Expand Down Expand Up @@ -82,8 +81,7 @@ public void setSequenceNumber(long seq) {
}

@Override
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv)
throws InvalidKeyException, InvalidAlgorithmParameterException {
protected void initCipher(javax.crypto.Cipher cipher, Mode mode, byte[] key, byte[] iv) {
this.mode = mode;

cipherKey = getKeySpec(Arrays.copyOfRange(key, 0, CHACHA_KEY_SIZE));
Expand Down Expand Up @@ -127,31 +125,41 @@ public void updateAAD(byte[] data) {

@Override
public void update(byte[] input, int inputOffset, int inputLen) {
if (inputOffset != AAD_LENGTH) {
if (inputOffset != 0 && inputOffset != AAD_LENGTH) {
throw new IllegalArgumentException("updateAAD called with inputOffset " + inputOffset);
}

final int macInputLength = AAD_LENGTH + inputLen;

final int macInputLength = inputOffset + inputLen;
if (mode == Mode.Decrypt) {
byte[] macInput = new byte[macInputLength];
System.arraycopy(encryptedAad, 0, macInput, 0, AAD_LENGTH);
System.arraycopy(input, AAD_LENGTH, macInput, AAD_LENGTH, inputLen);
final byte[] macInput = new byte[macInputLength];

if (inputOffset == 0) {
// Handle decryption without AAD
System.arraycopy(input, 0, macInput, 0, inputLen);
} else {
// Handle decryption with previous AAD from updateAAD()
System.arraycopy(encryptedAad, 0, macInput, 0, AAD_LENGTH);
System.arraycopy(input, AAD_LENGTH, macInput, AAD_LENGTH, inputLen);
}

byte[] expectedPolyTag = mac.doFinal(macInput);
byte[] actualPolyTag = Arrays.copyOfRange(input, macInputLength, macInputLength + POLY_TAG_LENGTH);
if (!Arrays.equals(actualPolyTag, expectedPolyTag)) {
final byte[] expectedPolyTag = mac.doFinal(macInput);
final byte[] actualPolyTag = Arrays.copyOfRange(input, macInputLength, macInputLength + POLY_TAG_LENGTH);
if (!MessageDigest.isEqual(actualPolyTag, expectedPolyTag)) {
throw new SSHRuntimeException("MAC Error");
}
}

try {
cipher.update(input, AAD_LENGTH, inputLen, input, AAD_LENGTH);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("Error updating data through cipher", e);
}
try {
cipher.update(input, inputOffset, inputLen, input, inputOffset);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("ChaCha20 decryption failed", e);
}
} else {
try {
cipher.update(input, AAD_LENGTH, inputLen, input, AAD_LENGTH);
} catch (GeneralSecurityException e) {
throw new SSHRuntimeException("ChaCha20 encryption failed", e);
}

if (mode == Mode.Encrypt) {
byte[] macInput = Arrays.copyOf(input, macInputLength);
byte[] polyTag = mac.doFinal(macInput);
System.arraycopy(polyTag, 0, input, macInputLength, POLY_TAG_LENGTH);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.hierynomus.sshj.common.KeyAlgorithm;
import com.hierynomus.sshj.common.KeyDecryptionFailedException;
import com.hierynomus.sshj.transport.cipher.BlockCiphers;
import com.hierynomus.sshj.transport.cipher.ChachaPolyCiphers;
import com.hierynomus.sshj.transport.cipher.GcmCiphers;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
Expand Down Expand Up @@ -83,6 +84,7 @@ public class OpenSSHKeyV1KeyFile extends BaseFileKeyProvider {
SUPPORTED_CIPHERS.put(BlockCiphers.AES256CTR().getName(), BlockCiphers.AES256CTR());
SUPPORTED_CIPHERS.put(GcmCiphers.AES256GCM().getName(), GcmCiphers.AES256GCM());
SUPPORTED_CIPHERS.put(GcmCiphers.AES128GCM().getName(), GcmCiphers.AES128GCM());
SUPPORTED_CIPHERS.put(ChachaPolyCiphers.CHACHA_POLY_OPENSSH().getName(), ChachaPolyCiphers.CHACHA_POLY_OPENSSH());
}

private PublicKey pubKey;
Expand Down Expand Up @@ -192,7 +194,7 @@ private byte[] readEncryptedPrivateKey(final byte[] privateKeyEncoded, final Pla
if (bufferRemaining == 0) {
encryptedPrivateKey = privateKeyEncoded;
} else {
// Read Authentication Tag for AES-GCM
// Read Authentication Tag for AES-GCM or ChaCha20-Poly1305
final byte[] authenticationTag = new byte[bufferRemaining];
inputBuffer.readRawBytes(authenticationTag);

Expand Down Expand Up @@ -314,7 +316,7 @@ private KeyPair readUnencrypted(final PlainBuffer keyBuffer, final PublicKey pub
int checkInt1 = keyBuffer.readUInt32AsInt(); // uint32 checkint1
int checkInt2 = keyBuffer.readUInt32AsInt(); // uint32 checkint2
if (checkInt1 != checkInt2) {
throw new KeyDecryptionFailedException();
throw new KeyDecryptionFailedException(new EncryptionException("OpenSSH Private Key integer comparison failed"));
}
// The private key section contains both the public key and the private key
String keyType = keyBuffer.readString(); // string keytype
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ public void shouldLoadProtectedEd25519PrivateKeyAES256GCM() throws IOException {
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_aes256-gcm", "sshjtest", true);
}

@Test
public void shouldLoadProtectedEd25519PrivateKeyChaCha20Poly1305() throws IOException {
checkOpenSSHKeyV1("src/test/resources/keytypes/ed25519_chacha20-poly1305", "sshjtest", false);
}

@Test
public void shouldFailOnIncorrectPassphraseAfterRetries() {
assertThrows(KeyDecryptionFailedException.class, () -> {
Expand Down
9 changes: 9 additions & 0 deletions src/test/resources/keytypes/ed25519_chacha20-poly1305
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm
JjcnlwdAAAABgAAAAQilqbRL4q3X9kEqWdTsD5/gAAABAAAAABAAAAMwAAAAtzc2gtZWQy
NTUxOQAAACCpvtXUZPONb1XDjLkHmP5mQrGryGaQsA68Nb+OAjaaEgAAAJh+Repmt76g31
jlD1ITaJU298ZU3rFWgA/Hs3xnOTNPjhMMu9nzfoZAu0fraE1MBVaEgNKRpw7SG+2eDBOo
3fvN3lF15i7Q8YHZd9alfcUg3FrvBzjd0Edx4AQxbSueibPFaqnwmVk/YzDiQHwlyWfA1x
HbqxrbJf1S0i8Bt5OjLK6woGk0/lfWJmy82xIa1sa3ONkPVjaJncm/f2SKV7t2k1UP9/jx
dLA=
-----END OPENSSH PRIVATE KEY-----
1 change: 1 addition & 0 deletions src/test/resources/keytypes/ed25519_chacha20-poly1305.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKm+1dRk841vVcOMuQeY/mZCsavIZpCwDrw1v44CNpoS

0 comments on commit 7f852d9

Please sign in to comment.