Replace lambdaworks:scrypt with libsodium-jni to match C implementation, remove dependency on opal-crypto and its providers.
This commit is contained in:
parent
1cfc199541
commit
f41f07f0ae
@ -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'
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user