Fully replace Java mpw algorithm implementation with proxy to standard C implementation.
This commit is contained in:
@@ -13,7 +13,8 @@ dependencies {
|
||||
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'
|
||||
api group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||
api group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
|
||||
api group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
api group: 'com.google.code.findbugs', name: 'findbugs-annotations', version: '3.0.1'
|
||||
|
||||
lib project( path: ':masterpassword-core', configuration: 'default' )
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public abstract class MPAlgorithm {
|
||||
* @param fullName The name of the user whose identity is described by the key.
|
||||
* @param masterPassword The user's secret that authenticates his access to the identity.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract byte[] masterKey(String fullName, char[] masterPassword);
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,7 @@ public abstract class MPAlgorithm {
|
||||
* @param keyPurpose The action that the user aims to undertake with this key.
|
||||
* @param keyContext An action-specific context within which to scope the key.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext);
|
||||
|
||||
@@ -63,31 +65,11 @@ public abstract class MPAlgorithm {
|
||||
* @param resultType The template to base the site key's encoding on.
|
||||
* @param resultParam A parameter that provides contextual data specific to the type template.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract String siteResult(byte[] masterKey, byte[] siteKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
/**
|
||||
* The result for {@link #siteResult(byte[], byte[], String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}
|
||||
* for the case where {@code resultType} is a {@link MPResultTypeClass#Template}.
|
||||
*/
|
||||
public abstract String siteResultFromTemplate(byte[] masterKey, byte[] siteKey,
|
||||
MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
/**
|
||||
* The result for {@link #siteResult(byte[], byte[], String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}
|
||||
* for the case where {@code resultType} is a {@link MPResultTypeClass#Stateful}.
|
||||
*/
|
||||
public abstract String siteResultFromState(byte[] masterKey, byte[] siteKey,
|
||||
MPResultType resultType, String resultParam);
|
||||
|
||||
/**
|
||||
* The result for {@link #siteResult(byte[], byte[], String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}
|
||||
* for the case where {@code resultType} is a {@link MPResultTypeClass#Derive}.
|
||||
*/
|
||||
public abstract String siteResultFromDerive(byte[] masterKey, byte[] siteKey,
|
||||
MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
/**
|
||||
* For {@link MPResultTypeClass#Stateful} {@code resultType}s, generate the {@code resultParam} to use with the
|
||||
* {@link #siteResult(byte[], byte[], String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)} call
|
||||
@@ -96,6 +78,7 @@ public abstract class MPAlgorithm {
|
||||
* @param resultType The template to base the site key's encoding on.
|
||||
* @param resultParam A parameter that provides contextual data specific to the type template.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract String siteState(byte[] masterKey, byte[] siteKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, String resultParam);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-21
|
||||
*/
|
||||
public class MPAlgorithmException extends MPException {
|
||||
|
||||
public MPAlgorithmException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPAlgorithmException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-06-03
|
||||
*/
|
||||
public class MPException extends Exception {
|
||||
|
||||
public MPException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,13 @@ package com.lyndir.masterpassword;
|
||||
/**
|
||||
* @author lhunath, 2017-09-21
|
||||
*/
|
||||
public class MPKeyUnavailableException extends Exception {
|
||||
public class MPKeyUnavailableException extends MPException {
|
||||
|
||||
public MPKeyUnavailableException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPKeyUnavailableException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class MPMasterKey {
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public byte[] getKeyID(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException {
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return algorithm.toID( masterKey( algorithm ) );
|
||||
}
|
||||
@@ -83,28 +83,30 @@ public class MPMasterKey {
|
||||
}
|
||||
|
||||
private byte[] masterKey(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException {
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
Preconditions.checkArgument( masterPassword.length > 0 );
|
||||
|
||||
if (invalidated)
|
||||
throw new MPKeyUnavailableException();
|
||||
throw new MPKeyUnavailableException( "Master key was invalidated." );
|
||||
|
||||
byte[] key = keyByVersion.get( algorithm.version() );
|
||||
if (key == null) {
|
||||
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
||||
if (masterKey == null) {
|
||||
logger.trc( "-- mpw_masterKey (algorithm: %s)", algorithm );
|
||||
logger.trc( "fullName: %s", fullName );
|
||||
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex(
|
||||
algorithm.toID( algorithm.toBytes( masterPassword ) ) ) );
|
||||
|
||||
keyByVersion.put( algorithm.version(), key = algorithm.masterKey( fullName, masterPassword ) );
|
||||
keyByVersion.put( algorithm.version(), masterKey = algorithm.masterKey( fullName, masterPassword ) );
|
||||
}
|
||||
if (masterKey == null)
|
||||
throw new MPAlgorithmException( "Could not derive master key." );
|
||||
|
||||
return key;
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPKeyUnavailableException {
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
@@ -115,7 +117,11 @@ public class MPMasterKey {
|
||||
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
|
||||
logger.trc( "keyContext: %s", keyContext );
|
||||
|
||||
return algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
byte[] siteKey = algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
if (siteKey == null)
|
||||
throw new MPAlgorithmException( "Could not derive site key." );
|
||||
|
||||
return siteKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,7 +141,7 @@ public class MPMasterKey {
|
||||
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, @Nullable final String resultParam)
|
||||
throws MPKeyUnavailableException {
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
||||
@@ -144,8 +150,12 @@ public class MPMasterKey {
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %s", resultParam );
|
||||
|
||||
return algorithm.siteResult(
|
||||
String siteResult = algorithm.siteResult(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
if (siteResult == null)
|
||||
throw new MPAlgorithmException( "Could not derive site result." );
|
||||
|
||||
return siteResult;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +174,7 @@ public class MPMasterKey {
|
||||
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, final String resultParam)
|
||||
throws MPKeyUnavailableException {
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
Preconditions.checkNotNull( resultParam );
|
||||
Preconditions.checkArgument( !resultParam.isEmpty() );
|
||||
@@ -176,7 +186,11 @@ public class MPMasterKey {
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( algorithm.mpw_charset() ).length, resultParam );
|
||||
|
||||
return algorithm.siteState(
|
||||
String siteState = algorithm.siteState(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
if (siteState == null)
|
||||
throw new MPAlgorithmException( "Could not derive site state." );
|
||||
|
||||
return siteState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,16 @@
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
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.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.MessageDigests;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.*;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.nio.charset.*;
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
|
||||
/**
|
||||
@@ -58,245 +49,79 @@ public class MPAlgorithmV0 extends MPAlgorithm {
|
||||
|
||||
protected final Logger logger = Logger.get( getClass() );
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] masterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
byte[] fullNameBytes = fullName.getBytes( mpw_charset() );
|
||||
byte[] fullNameLengthBytes = toBytes( fullName.length() );
|
||||
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
|
||||
CharsetEncoder encoder = mpw_charset().newEncoder();
|
||||
byte[] masterPasswordBytes = new byte[(int) (masterPassword.length * (double) encoder.maxBytesPerChar()) + 1];
|
||||
try {
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
ByteBuffer masterPasswordBuffer = ByteBuffer.wrap( masterPasswordBytes );
|
||||
|
||||
String keyScope = MPKeyPurpose.Authentication.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
CoderResult result = encoder.encode( CharBuffer.wrap( masterPassword ), masterPasswordBuffer, true );
|
||||
if (!result.isUnderflow())
|
||||
result.throwException();
|
||||
result = encoder.flush( masterPasswordBuffer );
|
||||
if (!result.isUnderflow())
|
||||
result.throwException();
|
||||
|
||||
// Calculate the master key salt.
|
||||
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
|
||||
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( mpw_charset() ), fullNameLengthBytes, fullNameBytes );
|
||||
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( toID( masterKeySalt ) ) );
|
||||
|
||||
// Calculate the master key.
|
||||
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
|
||||
scrypt_N(), scrypt_r(), scrypt_p() );
|
||||
byte[] masterPasswordBytes = toBytes( masterPassword );
|
||||
byte[] masterKey = scrypt( masterPasswordBytes, masterKeySalt, mpw_dkLen() );
|
||||
Arrays.fill( masterKeySalt, (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 ) ) );
|
||||
|
||||
return masterKey;
|
||||
return _masterKey( fullName, masterPasswordBytes, version().toInt() );
|
||||
}
|
||||
catch (final CharacterCodingException e) {
|
||||
throw new IllegalStateException( e );
|
||||
}
|
||||
finally {
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected byte[] scrypt(final byte[] secret, final byte[] salt, final int keySize) {
|
||||
byte[] buffer = new byte[keySize];
|
||||
if (_scrypt( secret, salt, scrypt_N(), scrypt_r(), scrypt_p(), buffer ) < 0)
|
||||
return null;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
protected native int _scrypt(byte[] passwd, byte[] salt, int N, int r, int p, byte[] buf);
|
||||
protected native byte[] _masterKey(final String fullName, final byte[] masterPassword, final int algorithmVersion);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter,
|
||||
public byte[] siteKey(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext) {
|
||||
|
||||
String keyScope = keyPurpose.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (mpw_otp_window() * 1000)) * mpw_otp_window() );
|
||||
|
||||
// Calculate the site seed.
|
||||
byte[] siteNameBytes = siteName.getBytes( mpw_charset() );
|
||||
byte[] siteNameLengthBytes = toBytes( siteName.length() );
|
||||
byte[] siteCounterBytes = toBytes( siteCounter );
|
||||
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( mpw_charset() );
|
||||
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: toBytes( keyContextBytes.length );
|
||||
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
|
||||
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( mpw_charset() ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (keyContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
|
||||
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( toID( sitePasswordInfo ) ) );
|
||||
|
||||
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( toID( masterKey ) ) );
|
||||
byte[] sitePasswordSeedBytes = mpw_digest().of( masterKey, sitePasswordInfo );
|
||||
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( toID( sitePasswordSeedBytes ) ) );
|
||||
|
||||
return sitePasswordSeedBytes;
|
||||
return _siteKey( masterKey, siteName, siteCounter.longValue(), keyPurpose.toInt(), keyContext, version().toInt() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native byte[] _siteKey(final byte[] masterKey, final String siteName, final long siteCounter,
|
||||
final int keyPurpose, @Nullable final String keyContext, final int version);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
switch (resultType.getTypeClass()) {
|
||||
case Template:
|
||||
return siteResultFromTemplate( masterKey, siteKey, resultType, resultParam );
|
||||
case Stateful:
|
||||
return siteResultFromState( masterKey, siteKey, resultType, Preconditions.checkNotNull( resultParam ) );
|
||||
case Derive:
|
||||
return siteResultFromDerive( masterKey, siteKey, resultType, resultParam );
|
||||
}
|
||||
|
||||
throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() );
|
||||
return _siteResult( masterKey, siteKey, siteName, siteCounter.longValue(),
|
||||
keyPurpose.toInt(), keyContext, resultType.getType(), resultParam, version().toInt() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String siteResultFromTemplate(final byte[] masterKey, final byte[] siteKey,
|
||||
final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
int[] _siteKey = new int[siteKey.length];
|
||||
for (int i = 0; i < siteKey.length; ++i) {
|
||||
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() );
|
||||
Arrays.fill( buf.array(), (byte) ((siteKey[i] > 0)? 0x00: 0xFF) );
|
||||
buf.position( 2 );
|
||||
buf.put( siteKey[i] ).rewind();
|
||||
_siteKey[i] = buf.getInt() & 0xFFFF;
|
||||
}
|
||||
|
||||
// Determine the template.
|
||||
Preconditions.checkState( _siteKey.length > 0 );
|
||||
int templateIndex = _siteKey[0];
|
||||
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
|
||||
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
|
||||
|
||||
// Encode the password from the seed using the template.
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = _siteKey[i + 1];
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( " - class: %c, index: %5d (0x%2H) => character: %c",
|
||||
characterClass.getIdentifier(), characterIndex, _siteKey[i + 1], passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
logger.trc( " => password: %s", password );
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String siteResultFromState(final byte[] masterKey, final byte[] siteKey,
|
||||
final MPResultType resultType, final String resultParam) {
|
||||
|
||||
Preconditions.checkNotNull( resultParam );
|
||||
Preconditions.checkArgument( !resultParam.isEmpty() );
|
||||
|
||||
// Base64-decode
|
||||
byte[] cipherBuf = BaseEncoding.base64().decode( resultParam );
|
||||
logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
|
||||
|
||||
// Decrypt
|
||||
byte[] plainBuf = aes_decrypt( cipherBuf, masterKey );
|
||||
String plainText = mpw_charset().decode( ByteBuffer.wrap( plainBuf ) ).toString();
|
||||
logger.trc( "decrypted -> plainText: %d bytes = %s = %s", plainBuf.length, plainText, CodeUtils.encodeHex( plainBuf ) );
|
||||
|
||||
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) {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String siteResultFromDerive(final byte[] masterKey, final byte[] siteKey,
|
||||
final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
throw new UnsupportedOperationException( "TODO" );
|
||||
|
||||
// if (resultType == MPResultType.DeriveKey) {
|
||||
// int resultParamInt = ConversionUtils.toIntegerNN( resultParam );
|
||||
// if (resultParamInt == 0)
|
||||
// resultParamInt = mpw_keySize_max();
|
||||
// if ((resultParamInt < mpw_keySize_min()) || (resultParamInt > mpw_keySize_max()) || ((resultParamInt % 8) != 0))
|
||||
// throw logger.bug( "Parameter is not a valid key size (should be 128 - 512): %s", resultParam );
|
||||
// int keySize = resultParamInt / 8;
|
||||
// logger.trc( "keySize: %d", keySize );
|
||||
//
|
||||
// // Derive key
|
||||
// byte[] resultKey = null; // TODO: mpw_kdf_blake2b()( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
|
||||
// if (resultKey == null)
|
||||
// throw logger.bug( "Could not derive result key." );
|
||||
//
|
||||
// // Base64-encode
|
||||
// String b64Key = Preconditions.checkNotNull( BaseEncoding.base64().encode( resultKey ) );
|
||||
// logger.trc( "b64 encoded -> key: %s", b64Key );
|
||||
//
|
||||
// return b64Key;
|
||||
// } else
|
||||
// throw logger.bug( "Unsupported derived password type: %s", resultType );
|
||||
}
|
||||
@Nullable
|
||||
protected native String _siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final long siteCounter,
|
||||
final int keyPurpose, @Nullable final String keyContext,
|
||||
final int resultType, @Nullable final String resultParam, final int algorithmVersion);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, final String resultParam) {
|
||||
|
||||
// Encrypt
|
||||
byte[] cipherBuf = aes_encrypt( resultParam.getBytes( mpw_charset() ), masterKey );
|
||||
logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
|
||||
|
||||
// Base64-encode
|
||||
String cipherText = Preconditions.checkNotNull( BaseEncoding.base64().encode( cipherBuf ) );
|
||||
logger.trc( "b64 encoded -> cipherText: %s", cipherText );
|
||||
|
||||
return cipherText;
|
||||
return _siteState( masterKey, siteKey, siteName, siteCounter.longValue(),
|
||||
keyPurpose.toInt(), keyContext, resultType.getType(), resultParam, version().toInt() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native String _siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final long siteCounter,
|
||||
final int keyPurpose, @Nullable final String keyContext,
|
||||
final int resultType, final String resultParam, final int algorithmVersion);
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedBytes;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
@@ -30,32 +27,6 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class MPAlgorithmV1 extends MPAlgorithmV0 {
|
||||
|
||||
@Override
|
||||
public String siteResultFromTemplate(final byte[] masterKey, final byte[] siteKey,
|
||||
final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
// Determine the template.
|
||||
Preconditions.checkState( siteKey.length > 0 );
|
||||
int templateIndex = UnsignedBytes.toInt( siteKey[0] );
|
||||
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
|
||||
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
|
||||
|
||||
// Encode the password from the seed using the template.
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = UnsignedBytes.toInt( siteKey[i + 1] );
|
||||
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( " - class: %c, index: %3d (0x%2H) => character: %c",
|
||||
characterClass.getIdentifier(), characterIndex, siteKey[i + 1], passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
logger.trc( " => password: %s", password );
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,12 +18,7 @@
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPKeyPurpose;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
@@ -32,39 +27,6 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class MPAlgorithmV2 extends MPAlgorithmV1 {
|
||||
|
||||
@Override
|
||||
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext) {
|
||||
|
||||
String keyScope = keyPurpose.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// OTP counter value.
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (mpw_otp_window() * 1000)) * mpw_otp_window() );
|
||||
|
||||
// Calculate the site seed.
|
||||
byte[] siteNameBytes = siteName.getBytes( mpw_charset() );
|
||||
byte[] siteNameLengthBytes = toBytes( siteNameBytes.length );
|
||||
byte[] siteCounterBytes = toBytes( siteCounter );
|
||||
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( mpw_charset() );
|
||||
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: toBytes( keyContextBytes.length );
|
||||
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
|
||||
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
|
||||
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( mpw_charset() ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (keyContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
|
||||
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( toID( sitePasswordInfo ) ) );
|
||||
|
||||
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( toID( masterKey ) ) );
|
||||
byte[] sitePasswordSeedBytes = mpw_digest().of( masterKey, sitePasswordInfo );
|
||||
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( toID( sitePasswordSeedBytes ) ) );
|
||||
|
||||
return sitePasswordSeedBytes;
|
||||
}
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPKeyPurpose;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
@@ -31,35 +27,6 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class MPAlgorithmV3 extends MPAlgorithmV2 {
|
||||
|
||||
@Override
|
||||
public byte[] masterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
byte[] fullNameBytes = fullName.getBytes( mpw_charset() );
|
||||
byte[] fullNameLengthBytes = toBytes( fullNameBytes.length );
|
||||
|
||||
String keyScope = MPKeyPurpose.Authentication.getScope();
|
||||
logger.trc( "keyScope: %s", keyScope );
|
||||
|
||||
// Calculate the master key salt.
|
||||
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
|
||||
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
|
||||
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( mpw_charset() ), fullNameLengthBytes, fullNameBytes );
|
||||
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( toID( masterKeySalt ) ) );
|
||||
|
||||
// Calculate the master key.
|
||||
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
|
||||
scrypt_N(), scrypt_r(), scrypt_p() );
|
||||
byte[] masterPasswordBytes = toBytes( masterPassword );
|
||||
byte[] masterKey = scrypt( masterPasswordBytes, masterKeySalt, mpw_dkLen() );
|
||||
Arrays.fill( masterKeySalt, (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 ) ) );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user