2
0

Replace lambdaworks:scrypt with libsodium-jni to match C implementation, remove dependency on opal-crypto and its providers.

This commit is contained in:
Maarten Billemont 2018-05-19 19:58:37 -04:00
parent 1cfc199541
commit f41f07f0ae
4 changed files with 100 additions and 41 deletions

View File

@ -8,9 +8,8 @@ dependencies {
compile( group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1' ) { compile( group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1' ) {
exclude( module: 'joda-time' ) exclude( module: 'joda-time' )
} }
compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.7-p1'
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0' compile 'com.github.joshjdevl.libsodiumjni:libsodium-jni:1.0.6'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.5' compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.5'
compile group: 'org.jetbrains', name: 'annotations', version: '13.0' compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1' compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'

View File

@ -18,22 +18,27 @@
package com.lyndir.masterpassword.impl; package com.lyndir.masterpassword.impl;
import com.google.common.base.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.crypto.CryptUtils;
import com.lyndir.lhunath.opal.system.*; import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.nio.*; import java.nio.*;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays; import java.util.Arrays;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.crypto.BadPaddingException; import javax.crypto.*;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.libsodium.jni.Sodium;
/** /**
@ -43,6 +48,15 @@ import javax.crypto.IllegalBlockSizeException;
@SuppressWarnings("NewMethodNamingConvention") @SuppressWarnings("NewMethodNamingConvention")
public class MPAlgorithmV0 extends MPAlgorithm { public class MPAlgorithmV0 extends MPAlgorithm {
@SuppressWarnings("HardcodedFileSeparator")
protected static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
protected static final int AES_BLOCKSIZE = 128 /* bit */;
static {
if (Sodium.sodium_init() < 0)
throw new IllegalStateException( "Couldn't initialize libsodium." );
}
public final Version version = MPAlgorithm.Version.V0; public final Version version = MPAlgorithm.Version.V0;
protected final Logger logger = Logger.get( getClass() ); protected final Logger logger = Logger.get( getClass() );
@ -66,21 +80,25 @@ public class MPAlgorithmV0 extends MPAlgorithm {
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )", logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
scrypt_N(), scrypt_r(), scrypt_p() ); scrypt_N(), scrypt_r(), scrypt_p() );
byte[] masterPasswordBytes = toBytes( masterPassword ); byte[] masterPasswordBytes = toBytes( masterPassword );
byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes ); byte[] masterKey = scrypt( masterPasswordBytes, masterKeySalt, mpw_dkLen() );
Arrays.fill( masterKeySalt, (byte) 0 ); Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( masterPasswordBytes, (byte) 0 ); Arrays.fill( masterPasswordBytes, (byte) 0 );
if (masterKey == null)
throw new IllegalStateException( "Could not derive master key." );
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( toID( masterKey ) ) ); logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( toID( masterKey ) ) );
return masterKey; return masterKey;
} }
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) { @Nullable
try { protected byte[] scrypt(final byte[] secret, final byte[] salt, final int keySize) {
return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N(), scrypt_r(), scrypt_p(), mpw_dkLen() ); byte[] buffer = new byte[keySize];
} if (Sodium.crypto_pwhash_scryptsalsa208sha256_ll(
catch (final GeneralSecurityException e) { secret, secret.length, salt, salt.length,
throw logger.bug( e ); scrypt_N(), scrypt_r(), scrypt_p(), buffer, buffer.length ) < 0)
} return null;
return buffer;
} }
@Override @Override
@ -175,20 +193,65 @@ public class MPAlgorithmV0 extends MPAlgorithm {
Preconditions.checkNotNull( resultParam ); Preconditions.checkNotNull( resultParam );
Preconditions.checkArgument( !resultParam.isEmpty() ); Preconditions.checkArgument( !resultParam.isEmpty() );
try {
// Base64-decode // Base64-decode
byte[] cipherBuf = CryptUtils.decodeBase64( resultParam ); byte[] cipherBuf = BaseEncoding.base64().decode( resultParam );
logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Decrypt // Decrypt
byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true ); byte[] plainBuf = aes_decrypt( cipherBuf, masterKey );
String plainText = mpw_charset().decode( ByteBuffer.wrap( plainBuf ) ).toString(); String plainText = mpw_charset().decode( ByteBuffer.wrap( plainBuf ) ).toString();
logger.trc( "decrypted -> plainText: %d bytes = %s = %s", plainBuf.length, plainText, CodeUtils.encodeHex( plainBuf ) ); logger.trc( "decrypted -> plainText: %d bytes = %s = %s", plainBuf.length, plainText, CodeUtils.encodeHex( plainBuf ) );
return plainText; return plainText;
} }
protected byte[] aes_encrypt(final byte[] buf, final byte[] key) {
return aes( true, buf, key );
}
protected byte[] aes_decrypt(final byte[] buf, final byte[] key) {
return aes( false, buf, key );
}
protected byte[] aes(final boolean encrypt, final byte[] buf, final byte[] key) {
int blockByteSize = AES_BLOCKSIZE / Byte.SIZE;
byte[] blockSizedKey = key;
if (blockSizedKey.length != blockByteSize) {
blockSizedKey = new byte[blockByteSize];
System.arraycopy( key, 0, blockSizedKey, 0, blockByteSize );
}
// Encrypt data with key.
try {
Cipher cipher = Cipher.getInstance( AES_TRANSFORMATION );
AlgorithmParameterSpec parameters = new IvParameterSpec( new byte[blockByteSize] );
cipher.init( encrypt? Cipher.ENCRYPT_MODE: Cipher.DECRYPT_MODE, new SecretKeySpec( blockSizedKey, "AES" ), parameters );
return cipher.doFinal( buf );
}
catch (final NoSuchAlgorithmException e) {
throw new IllegalStateException(
strf( "Cipher transformation: %s, is not valid or not supported by the provider.", AES_TRANSFORMATION ), e );
}
catch (final NoSuchPaddingException e) {
throw new IllegalStateException(
strf( "Cipher transformation: %s, padding scheme is not supported by the provider.", AES_TRANSFORMATION ), e );
}
catch (final BadPaddingException e) { catch (final BadPaddingException e) {
throw Throwables.propagate( e ); throw new IllegalArgumentException(
strf( "Message is incorrectly padded for cipher transformation: %s.", AES_TRANSFORMATION ), e );
}
catch (final IllegalBlockSizeException e) {
throw new IllegalArgumentException(
strf( "Message size is invalid for cipher transformation: %s.", AES_TRANSFORMATION ), e );
}
catch (final InvalidKeyException e) {
throw new IllegalArgumentException(
strf( "Key is inappropriate for cipher transformation: %s.", AES_TRANSFORMATION ), e );
}
catch (final InvalidAlgorithmParameterException e) {
throw new IllegalStateException(
strf( "IV is inappropriate for cipher transformation: %s.", AES_TRANSFORMATION ), e );
} }
} }
@ -211,7 +274,7 @@ public class MPAlgorithmV0 extends MPAlgorithm {
throw logger.bug( "Could not derive result key." ); throw logger.bug( "Could not derive result key." );
// Base64-encode // Base64-encode
String b64Key = Preconditions.checkNotNull( CryptUtils.encodeBase64( resultKey ) ); String b64Key = Preconditions.checkNotNull( BaseEncoding.base64().encode( resultKey ) );
logger.trc( "b64 encoded -> key: %s", b64Key ); logger.trc( "b64 encoded -> key: %s", b64Key );
return b64Key; return b64Key;
@ -224,21 +287,16 @@ public class MPAlgorithmV0 extends MPAlgorithm {
final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
final MPResultType resultType, final String resultParam) { final MPResultType resultType, final String resultParam) {
try {
// Encrypt // Encrypt
byte[] cipherBuf = CryptUtils.encrypt( resultParam.getBytes( mpw_charset() ), masterKey, true ); byte[] cipherBuf = aes_encrypt( resultParam.getBytes( mpw_charset() ), masterKey );
logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Base64-encode // Base64-encode
String cipherText = Preconditions.checkNotNull( CryptUtils.encodeBase64( cipherBuf ) ); String cipherText = Preconditions.checkNotNull( BaseEncoding.base64().encode( cipherBuf ) );
logger.trc( "b64 encoded -> cipherText: %s", cipherText ); logger.trc( "b64 encoded -> cipherText: %s", cipherText );
return cipherText; return cipherText;
} }
catch (final IllegalBlockSizeException e) {
throw logger.bug( e );
}
}
// Configuration // Configuration

View File

@ -50,9 +50,11 @@ public class MPAlgorithmV3 extends MPAlgorithmV2 {
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )", logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
scrypt_N(), scrypt_r(), scrypt_p() ); scrypt_N(), scrypt_r(), scrypt_p() );
byte[] masterPasswordBytes = toBytes( masterPassword ); byte[] masterPasswordBytes = toBytes( masterPassword );
byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes ); byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes, mpw_dkLen() );
Arrays.fill( masterKeySalt, (byte) 0 ); Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( masterPasswordBytes, (byte) 0 ); Arrays.fill( masterPasswordBytes, (byte) 0 );
if (masterKey == null)
throw new IllegalStateException( "Could not derive master key." );
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( toID( masterKey ) ) ); logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( toID( masterKey ) ) );
return masterKey; return masterKey;

@ -1 +1 @@
Subproject commit 54bd876ed6b8cebba3fdfe8b546d1783894d5401 Subproject commit e7a3a2a01dcf4ef982dcdc5d0f9512a0eef3e2aa