251 lines
8.2 KiB
Java
251 lines
8.2 KiB
Java
//==============================================================================
|
|
// 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.impl;
|
|
|
|
import com.google.common.base.Charsets;
|
|
import com.google.common.primitives.UnsignedInteger;
|
|
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.*;
|
|
import java.util.Arrays;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
|
|
/**
|
|
* @author lhunath, 2014-08-30
|
|
* @see MPAlgorithm.Version#V0
|
|
*/
|
|
@SuppressWarnings("NewMethodNamingConvention")
|
|
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 {
|
|
Native.load( MPAlgorithmV0.class, "mpw" );
|
|
}
|
|
|
|
protected final Logger logger = Logger.get( getClass() );
|
|
|
|
@Nullable
|
|
@Override
|
|
public byte[] masterKey(final String fullName, final char[] masterPassword) {
|
|
|
|
// 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 );
|
|
|
|
CoderResult result = encoder.encode( CharBuffer.wrap( masterPassword ), masterPasswordBuffer, true );
|
|
if (result.isError())
|
|
throw new IllegalStateException( result.toString() );
|
|
result = encoder.flush( masterPasswordBuffer );
|
|
if (result.isError())
|
|
throw new IllegalStateException( result.toString() );
|
|
|
|
return _masterKey( fullName, masterPasswordBytes, version().toInt() );
|
|
}
|
|
finally {
|
|
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
protected native byte[] _masterKey(final String fullName, final byte[] masterPassword, final int algorithmVersion);
|
|
|
|
@Nullable
|
|
@Override
|
|
public byte[] siteKey(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter,
|
|
final MPKeyPurpose keyPurpose, @Nullable final String keyContext) {
|
|
|
|
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) {
|
|
|
|
return _siteResult( masterKey, siteKey, siteName, siteCounter.longValue(),
|
|
keyPurpose.toInt(), keyContext, resultType.getType(), resultParam, version().toInt() );
|
|
}
|
|
|
|
@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) {
|
|
|
|
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
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Version version() {
|
|
return MPAlgorithm.Version.V0;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public UnsignedInteger mpw_default_counter() {
|
|
return UnsignedInteger.ONE;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MPResultType mpw_default_result_type() {
|
|
return MPResultType.GeneratedLong;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MPResultType mpw_default_login_type() {
|
|
return MPResultType.GeneratedName;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MPResultType mpw_default_answer_type() {
|
|
return MPResultType.GeneratedPhrase;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public Charset mpw_charset() {
|
|
return Charsets.UTF_8;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public ByteOrder mpw_byteOrder() {
|
|
return ByteOrder.BIG_ENDIAN;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MessageDigests mpw_hash() {
|
|
return MessageDigests.SHA256;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public MessageAuthenticationDigests mpw_digest() {
|
|
return MessageAuthenticationDigests.HmacSHA256;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public int mpw_dkLen() {
|
|
return 64;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public int mpw_keySize_min() {
|
|
return 128;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public int mpw_keySize_max() {
|
|
return 512;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public long mpw_otp_window() {
|
|
return 5 * 60 /* s */;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public int scrypt_N() {
|
|
return 32768;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public int scrypt_r() {
|
|
return 8;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("MagicNumber")
|
|
public int scrypt_p() {
|
|
return 2;
|
|
}
|
|
|
|
// Utilities
|
|
|
|
@Nonnull
|
|
@Override
|
|
public byte[] toBytes(final int number) {
|
|
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public byte[] toBytes(final UnsignedInteger number) {
|
|
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public byte[] toBytes(final char[] characters) {
|
|
ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) );
|
|
|
|
byte[] bytes = new byte[byteBuffer.remaining()];
|
|
byteBuffer.get( bytes );
|
|
|
|
Arrays.fill( byteBuffer.array(), (byte) 0 );
|
|
return bytes;
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public byte[] toID(final byte[] bytes) {
|
|
return mpw_hash().of( bytes );
|
|
}
|
|
}
|