Refactored the different crypto classes into their own classes
This commit is contained in:
parent
6ece94345d
commit
1d3a9ff71b
File diff suppressed because it is too large
Load Diff
644
Dorkbox-Util/src/dorkbox/util/crypto/CryptoAES.java
Normal file
644
Dorkbox-Util/src/dorkbox/util/crypto/CryptoAES.java
Normal file
|
@ -0,0 +1,644 @@
|
||||||
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.CipherParameters;
|
||||||
|
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES crypto functions
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused", "Duplicates"})
|
||||||
|
public final
|
||||||
|
class CryptoAES {
|
||||||
|
private static final int ivSize = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] encryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||||
|
byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data, logger);
|
||||||
|
|
||||||
|
int length = encryptAES.length;
|
||||||
|
|
||||||
|
byte[] out = new byte[length + ivSize];
|
||||||
|
System.arraycopy(aesIV, 0, out, 0, ivSize);
|
||||||
|
System.arraycopy(encryptAES, 0, out, ivSize, length);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
byte[] encryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||||
|
|
||||||
|
byte[] encryptAES = encrypt(aesEngine, aesKey, aesIV, data, logger);
|
||||||
|
|
||||||
|
int length = encryptAES.length;
|
||||||
|
|
||||||
|
byte[] out = new byte[length + ivSize];
|
||||||
|
System.arraycopy(aesIV, 0, out, 0, ivSize);
|
||||||
|
System.arraycopy(encryptAES, 0, out, ivSize, length);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean encryptStreamWithIV(GCMBlockCipher aesEngine,
|
||||||
|
byte[] aesKey,
|
||||||
|
byte[] aesIV,
|
||||||
|
InputStream in,
|
||||||
|
OutputStream out,
|
||||||
|
Logger logger) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
out.write(aesIV);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
boolean encryptStreamWithIV(BufferedBlockCipher aesEngine,
|
||||||
|
byte[] aesKey,
|
||||||
|
byte[] aesIV,
|
||||||
|
InputStream in,
|
||||||
|
OutputStream out,
|
||||||
|
Logger logger) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
out.write(aesIV);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] encrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||||
|
int length = data.length;
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(true, aesIVAndKey);
|
||||||
|
|
||||||
|
int minSize = aesEngine.getOutputSize(length);
|
||||||
|
byte[] outBuf = new byte[minSize];
|
||||||
|
|
||||||
|
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outBuf.length == actualLength) {
|
||||||
|
return outBuf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byte[] result = new byte[actualLength];
|
||||||
|
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return length of encrypted data, -1 if there was an error.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
int encrypt(dorkbox.util.crypto.bouncycastle.GCMBlockCipher_ByteBuf aesEngine,
|
||||||
|
CipherParameters aesIVAndKey,
|
||||||
|
io.netty.buffer.ByteBuf inBuffer,
|
||||||
|
io.netty.buffer.ByteBuf outBuffer,
|
||||||
|
int length,
|
||||||
|
Logger logger) {
|
||||||
|
|
||||||
|
aesEngine.reset();
|
||||||
|
aesEngine.init(true, aesIVAndKey);
|
||||||
|
|
||||||
|
length = aesEngine.processBytes(inBuffer, outBuffer, length);
|
||||||
|
|
||||||
|
try {
|
||||||
|
length += aesEngine.doFinal(outBuffer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.debug("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// specify where the encrypted data is at
|
||||||
|
outBuffer.readerIndex(0);
|
||||||
|
outBuffer.writerIndex(length);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
byte[] encrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||||
|
int length = data.length;
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(true, aesIVAndKey);
|
||||||
|
|
||||||
|
int minSize = aesEngine.getOutputSize(length);
|
||||||
|
byte[] outBuf = new byte[minSize];
|
||||||
|
|
||||||
|
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outBuf.length == actualLength) {
|
||||||
|
return outBuf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byte[] result = new byte[actualLength];
|
||||||
|
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES encrypt from one stream to another.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
boolean encryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||||
|
byte[] buf = new byte[ivSize];
|
||||||
|
byte[] outbuf = new byte[512];
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(true, aesIVAndKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int bytesRead;
|
||||||
|
int bytesProcessed;
|
||||||
|
|
||||||
|
while ((bytesRead = in.read(buf)) >= 0) {
|
||||||
|
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||||
|
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
out.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES encrypt from one stream to another.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean encryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||||
|
|
||||||
|
byte[] buf = new byte[ivSize];
|
||||||
|
byte[] outbuf = new byte[512];
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(true, aesIVAndKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int bytesRead;
|
||||||
|
int bytesProcessed;
|
||||||
|
|
||||||
|
while ((bytesRead = in.read(buf)) >= 0) {
|
||||||
|
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||||
|
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
out.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES decrypt (if the aes IV is included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] decryptWithIV(GCMBlockCipher aesEngine, byte[] aesKey, byte[] data, Logger logger) {
|
||||||
|
byte[] aesIV = new byte[ivSize];
|
||||||
|
System.arraycopy(data, 0, aesIV, 0, ivSize);
|
||||||
|
|
||||||
|
byte[] in = new byte[data.length - ivSize];
|
||||||
|
System.arraycopy(data, ivSize, in, 0, in.length);
|
||||||
|
|
||||||
|
return decrypt(aesEngine, aesKey, aesIV, in, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES decrypt (if the aes IV is included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
byte[] decryptWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] data, Logger logger) {
|
||||||
|
byte[] aesIV = new byte[ivSize];
|
||||||
|
System.arraycopy(data, 0, aesIV, 0, ivSize);
|
||||||
|
|
||||||
|
byte[] in = new byte[data.length - ivSize];
|
||||||
|
System.arraycopy(data, ivSize, in, 0, in.length);
|
||||||
|
|
||||||
|
return decrypt(aesEngine, aesKey, aesIV, in, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES decrypt (if the aes IV is included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean decryptStreamWithIV(GCMBlockCipher aesEngine, byte[] aesKey, InputStream in, OutputStream out, Logger logger) {
|
||||||
|
byte[] aesIV = new byte[ivSize];
|
||||||
|
try {
|
||||||
|
in.read(aesIV, 0, ivSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES decrypt (if the aes IV is included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
boolean decryptStreamWithIV(BufferedBlockCipher aesEngine, byte[] aesKey, InputStream in, OutputStream out, Logger logger) {
|
||||||
|
byte[] aesIV = new byte[ivSize];
|
||||||
|
try {
|
||||||
|
in.read(aesIV, 0, ivSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptStream(aesEngine, aesKey, aesIV, in, out, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] decrypt(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||||
|
int length = data.length;
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(false, aesIVAndKey);
|
||||||
|
|
||||||
|
int minSize = aesEngine.getOutputSize(length);
|
||||||
|
byte[] outBuf = new byte[minSize];
|
||||||
|
|
||||||
|
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.debug("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
if (outBuf.length == actualLength) {
|
||||||
|
return outBuf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byte[] result = new byte[actualLength];
|
||||||
|
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return length of decrypted data, -1 if there was an error.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
int decrypt(dorkbox.util.crypto.bouncycastle.GCMBlockCipher_ByteBuf aesEngine,
|
||||||
|
ParametersWithIV aesIVAndKey,
|
||||||
|
io.netty.buffer.ByteBuf bufferWithData,
|
||||||
|
io.netty.buffer.ByteBuf bufferTempData,
|
||||||
|
int length,
|
||||||
|
Logger logger) {
|
||||||
|
|
||||||
|
aesEngine.reset();
|
||||||
|
aesEngine.init(false, aesIVAndKey);
|
||||||
|
|
||||||
|
// ignore the start position
|
||||||
|
// we also do NOT want to have the same start position for the altBuffer, since it could then grow larger than the buffer capacity.
|
||||||
|
length = aesEngine.processBytes(bufferWithData, bufferTempData, length);
|
||||||
|
|
||||||
|
try {
|
||||||
|
length += aesEngine.doFinal(bufferTempData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.debug("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferTempData.readerIndex(0);
|
||||||
|
bufferTempData.writerIndex(length);
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES decrypt (if we already know the aes IV -- and it's NOT included in the data)
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
byte[] decrypt(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, byte[] data, Logger logger) {
|
||||||
|
|
||||||
|
int length = data.length;
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(false, aesIVAndKey);
|
||||||
|
|
||||||
|
int minSize = aesEngine.getOutputSize(length);
|
||||||
|
byte[] outBuf = new byte[minSize];
|
||||||
|
|
||||||
|
int actualLength = aesEngine.processBytes(data, 0, length, outBuf, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
actualLength += aesEngine.doFinal(outBuf, actualLength);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outBuf.length == actualLength) {
|
||||||
|
return outBuf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byte[] result = new byte[actualLength];
|
||||||
|
System.arraycopy(outBuf, 0, result, 0, result.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES decrypt from one stream to another.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean decryptStream(GCMBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||||
|
byte[] buf = new byte[ivSize];
|
||||||
|
byte[] outbuf = new byte[512];
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(false, aesIVAndKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int bytesRead;
|
||||||
|
int bytesProcessed;
|
||||||
|
|
||||||
|
while ((bytesRead = in.read(buf)) >= 0) {
|
||||||
|
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||||
|
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
out.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>CONVENIENCE METHOD ONLY - DO NOT USE UNLESS YOU HAVE TO</b>
|
||||||
|
* <p/>
|
||||||
|
* Use GCM instead, as it's an authenticated cipher (and "regular" AES is not). This prevents tampering with the blocks of encrypted
|
||||||
|
* data.
|
||||||
|
* <p/>
|
||||||
|
* AES decrypt from one stream to another.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static
|
||||||
|
boolean decryptStream(BufferedBlockCipher aesEngine, byte[] aesKey, byte[] aesIV, InputStream in, OutputStream out, Logger logger) {
|
||||||
|
byte[] buf = new byte[ivSize];
|
||||||
|
byte[] outbuf = new byte[512];
|
||||||
|
|
||||||
|
CipherParameters aesIVAndKey = new ParametersWithIV(new KeyParameter(aesKey), aesIV);
|
||||||
|
aesEngine.init(false, aesIVAndKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int bytesRead;
|
||||||
|
int bytesProcessed;
|
||||||
|
|
||||||
|
while ((bytesRead = in.read(buf)) >= 0) {
|
||||||
|
bytesProcessed = aesEngine.processBytes(buf, 0, bytesRead, outbuf, 0);
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesProcessed = aesEngine.doFinal(outbuf, 0);
|
||||||
|
|
||||||
|
out.write(outbuf, 0, bytesProcessed);
|
||||||
|
out.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform AES cipher.", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
CryptoAES() {
|
||||||
|
}
|
||||||
|
}
|
97
Dorkbox-Util/src/dorkbox/util/crypto/CryptoDSA.java
Normal file
97
Dorkbox-Util/src/dorkbox/util/crypto/CryptoDSA.java
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||||
|
import org.bouncycastle.crypto.generators.DSAKeyPairGenerator;
|
||||||
|
import org.bouncycastle.crypto.generators.DSAParametersGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.DSAKeyGenerationParameters;
|
||||||
|
import org.bouncycastle.crypto.params.DSAParameters;
|
||||||
|
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||||
|
import org.bouncycastle.crypto.signers.DSASigner;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this is here just for keeping track of how this is done. This should correct and working, but should NOT be used, and instead use ECC
|
||||||
|
* crypto.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public final
|
||||||
|
class CryptoDSA {
|
||||||
|
/**
|
||||||
|
* Generates the DSA key (using RSA and SHA1)
|
||||||
|
* <p/>
|
||||||
|
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
|
||||||
|
DSAKeyPairGenerator keyGen = new DSAKeyPairGenerator();
|
||||||
|
|
||||||
|
DSAParametersGenerator dsaParametersGenerator = new DSAParametersGenerator();
|
||||||
|
dsaParametersGenerator.init(keyLength, 20, secureRandom);
|
||||||
|
DSAParameters generateParameters = dsaParametersGenerator.generateParameters();
|
||||||
|
|
||||||
|
DSAKeyGenerationParameters params = new DSAKeyGenerationParameters(secureRandom, generateParameters);
|
||||||
|
keyGen.init(params);
|
||||||
|
return keyGen.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will have the SHA1 hash calculated and used for the signature.
|
||||||
|
* <p/>
|
||||||
|
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||||
|
* <p/>
|
||||||
|
* The returned signature is the {r,s} signature array.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
BigInteger[] generateSignature(DSAPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] message) {
|
||||||
|
ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
|
||||||
|
|
||||||
|
DSASigner dsa = new DSASigner();
|
||||||
|
|
||||||
|
dsa.init(true, param);
|
||||||
|
|
||||||
|
|
||||||
|
SHA1Digest sha1Digest = new SHA1Digest();
|
||||||
|
byte[] checksum = new byte[sha1Digest.getDigestSize()];
|
||||||
|
|
||||||
|
sha1Digest.update(message, 0, message.length);
|
||||||
|
sha1Digest.doFinal(checksum, 0);
|
||||||
|
|
||||||
|
return dsa.generateSignature(checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will have the SHA1 hash calculated and used for the signature.
|
||||||
|
* <p/>
|
||||||
|
* Note: this is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||||
|
*
|
||||||
|
* @param signature
|
||||||
|
* is the {r,s} signature array.
|
||||||
|
*
|
||||||
|
* @return true if the signature is valid
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean verifySignature(DSAPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
|
||||||
|
SHA1Digest sha1Digest = new SHA1Digest();
|
||||||
|
byte[] checksum = new byte[sha1Digest.getDigestSize()];
|
||||||
|
|
||||||
|
sha1Digest.update(message, 0, message.length);
|
||||||
|
sha1Digest.doFinal(checksum, 0);
|
||||||
|
|
||||||
|
|
||||||
|
DSASigner dsa = new DSASigner();
|
||||||
|
|
||||||
|
dsa.init(false, publicKey);
|
||||||
|
|
||||||
|
return dsa.verifySignature(checksum, signature[0], signature[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
CryptoDSA() {
|
||||||
|
}
|
||||||
|
}
|
385
Dorkbox-Util/src/dorkbox/util/crypto/CryptoECC.java
Normal file
385
Dorkbox-Util/src/dorkbox/util/crypto/CryptoECC.java
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.CipherParameters;
|
||||||
|
import org.bouncycastle.crypto.Digest;
|
||||||
|
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA384Digest;
|
||||||
|
import org.bouncycastle.crypto.digests.SHA512Digest;
|
||||||
|
import org.bouncycastle.crypto.engines.IESEngine;
|
||||||
|
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
|
||||||
|
import org.bouncycastle.crypto.macs.HMac;
|
||||||
|
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.IESParameters;
|
||||||
|
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||||
|
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||||
|
import org.bouncycastle.jcajce.provider.util.DigestFactory;
|
||||||
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
|
import org.bouncycastle.math.ec.ECFieldElement;
|
||||||
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECC crypto functions
|
||||||
|
*/
|
||||||
|
public final
|
||||||
|
class CryptoECC {
|
||||||
|
public static final String p521_curve = "secp521r1";
|
||||||
|
public static final int macSize = 512;
|
||||||
|
// more info about ECC from: http://www.johannes-bauer.com/compsci/ecc/?menuid=4
|
||||||
|
// http://stackoverflow.com/questions/7419183/problems-implementing-ecdh-on-android-using-bouncycastle
|
||||||
|
// http://tools.ietf.org/html/draft-jivsov-openpgp-ecc-06#page-4
|
||||||
|
// http://www.nsa.gov/ia/programs/suiteb_cryptography/
|
||||||
|
// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java
|
||||||
|
// http://nelenkov.blogspot.com/2011/12/using-ecdh-on-android.html
|
||||||
|
// http://www.secg.org/collateral/sec1_final.pdf
|
||||||
|
static final String ECC_NAME = "EC";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses SHA512
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
IESEngine createEngine() {
|
||||||
|
return new IESEngine(new ECDHCBasicAgreement(), new KDF2BytesGenerator(new SHA384Digest()), new HMac(new SHA512Digest()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses SHA512
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
IESEngine createEngine(PaddedBufferedBlockCipher aesEngine) {
|
||||||
|
return new IESEngine(new ECDHCBasicAgreement(),
|
||||||
|
new KDF2BytesGenerator(new SHA384Digest()),
|
||||||
|
new HMac(new SHA512Digest()),
|
||||||
|
aesEngine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These parameters are shared between the two parties. These are a NONCE (use ONCE number!!)
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
IESParameters generateSharedParameters(SecureRandom secureRandom) {
|
||||||
|
|
||||||
|
int macSize = CryptoECC.macSize; // must be the MAC size
|
||||||
|
|
||||||
|
// MUST be random EACH TIME encrypt/sign happens!
|
||||||
|
byte[] derivation = new byte[macSize / 8];
|
||||||
|
byte[] encoding = new byte[macSize / 8];
|
||||||
|
|
||||||
|
secureRandom.nextBytes(derivation);
|
||||||
|
secureRandom.nextBytes(encoding);
|
||||||
|
|
||||||
|
return new IESParameters(derivation, encoding, macSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-256 ONLY!
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
IESWithCipherParameters generateSharedParametersWithCipher(SecureRandom secureRandom) {
|
||||||
|
int macSize = CryptoECC.macSize; // must be the MAC size
|
||||||
|
|
||||||
|
byte[] derivation = new byte[macSize / 8]; // MUST be random EACH TIME encrypt/sign happens!
|
||||||
|
byte[] encoding = new byte[macSize / 8];
|
||||||
|
|
||||||
|
secureRandom.nextBytes(derivation);
|
||||||
|
secureRandom.nextBytes(encoding);
|
||||||
|
|
||||||
|
return new IESWithCipherParameters(derivation, encoding, macSize, 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
AsymmetricCipherKeyPair generateKeyPair(String eccCurveName, SecureRandom secureRandom) {
|
||||||
|
ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(eccCurveName);
|
||||||
|
|
||||||
|
return generateKeyPair(eccSpec, secureRandom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
AsymmetricCipherKeyPair generateKeyPair(ECParameterSpec eccSpec, SecureRandom secureRandom) {
|
||||||
|
ECKeyGenerationParameters ecParams = new ECKeyGenerationParameters(new ECDomainParameters(eccSpec.getCurve(),
|
||||||
|
eccSpec.getG(),
|
||||||
|
eccSpec.getN()), secureRandom);
|
||||||
|
|
||||||
|
ECKeyPairGenerator ecKeyGen = new ECKeyPairGenerator();
|
||||||
|
ecKeyGen.init(ecParams);
|
||||||
|
|
||||||
|
return ecKeyGen.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECC encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] encrypt(IESEngine eccEngine,
|
||||||
|
CipherParameters private1,
|
||||||
|
CipherParameters public2,
|
||||||
|
IESParameters cipherParams,
|
||||||
|
byte[] message,
|
||||||
|
Logger logger) {
|
||||||
|
|
||||||
|
eccEngine.init(true, private1, public2, cipherParams);
|
||||||
|
|
||||||
|
//noinspection Duplicates
|
||||||
|
try {
|
||||||
|
return eccEngine.processBlock(message, 0, message.length);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform ECC cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECC decrypt data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] decrypt(IESEngine eccEngine,
|
||||||
|
CipherParameters private2,
|
||||||
|
CipherParameters public1,
|
||||||
|
IESParameters cipherParams,
|
||||||
|
byte[] encrypted,
|
||||||
|
Logger logger) {
|
||||||
|
|
||||||
|
eccEngine.init(false, private2, public1, cipherParams);
|
||||||
|
|
||||||
|
//noinspection Duplicates
|
||||||
|
try {
|
||||||
|
return eccEngine.processBlock(encrypted, 0, encrypted.length);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform ECC cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
boolean compare(ECPrivateKeyParameters privateA, ECPrivateKeyParameters privateB) {
|
||||||
|
ECDomainParameters parametersA = privateA.getParameters();
|
||||||
|
ECDomainParameters parametersB = privateB.getParameters();
|
||||||
|
|
||||||
|
// is it the same curve?
|
||||||
|
boolean equals = parametersA.getCurve()
|
||||||
|
.equals(parametersB.getCurve());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals = parametersA.getG()
|
||||||
|
.equals(parametersB.getG());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
equals = parametersA.getH()
|
||||||
|
.equals(parametersB.getH());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals = parametersA.getN()
|
||||||
|
.equals(parametersB.getN());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals = privateA.getD()
|
||||||
|
.equals(privateB.getD());
|
||||||
|
|
||||||
|
return equals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if publicA and publicB are NOT NULL, and are both equal to eachother
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"RedundantIfStatement", "SpellCheckingInspection"})
|
||||||
|
public static
|
||||||
|
boolean compare(ECPublicKeyParameters publicA, ECPublicKeyParameters publicB) {
|
||||||
|
if (publicA == null || publicB == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ECDomainParameters parametersA = publicA.getParameters();
|
||||||
|
ECDomainParameters parametersB = publicB.getParameters();
|
||||||
|
|
||||||
|
// is it the same curve?
|
||||||
|
boolean equals = parametersA.getCurve()
|
||||||
|
.equals(parametersB.getCurve());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals = parametersA.getG()
|
||||||
|
.equals(parametersB.getG());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
equals = parametersA.getH()
|
||||||
|
.equals(parametersB.getH());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
equals = parametersA.getN()
|
||||||
|
.equals(parametersB.getN());
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ECPoint normalizeA = publicA.getQ()
|
||||||
|
.normalize();
|
||||||
|
ECPoint normalizeB = publicB.getQ()
|
||||||
|
.normalize();
|
||||||
|
|
||||||
|
|
||||||
|
ECFieldElement xCoordA = normalizeA.getXCoord();
|
||||||
|
ECFieldElement xCoordB = normalizeB.getXCoord();
|
||||||
|
|
||||||
|
equals = xCoordA.equals(xCoordB);
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECFieldElement yCoordA = normalizeA.getYCoord();
|
||||||
|
ECFieldElement yCoordB = normalizeB.getYCoord();
|
||||||
|
|
||||||
|
equals = yCoordA.equals(yCoordB);
|
||||||
|
if (!equals) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
public static
|
||||||
|
boolean compare(IESParameters cipherAParams, IESParameters cipherBParams) {
|
||||||
|
if (!Arrays.equals(cipherAParams.getDerivationV(), cipherBParams.getDerivationV())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(cipherAParams.getEncodingV(), cipherBParams.getEncodingV())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipherAParams.getMacKeySize() != cipherBParams.getMacKeySize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
boolean compare(IESWithCipherParameters cipherAParams, IESWithCipherParameters cipherBParams) {
|
||||||
|
if (cipherAParams.getCipherKeySize() != cipherBParams.getCipherKeySize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only need to cast one side.
|
||||||
|
return compare((IESParameters) cipherAParams, cipherBParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will have the (digestName) hash calculated and used for the signature.
|
||||||
|
* <p/>
|
||||||
|
* The returned signature is the {r,s} signature array.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
BigInteger[] generateSignature(String digestName, ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] bytes) {
|
||||||
|
|
||||||
|
Digest digest = DigestFactory.getDigest(digestName);
|
||||||
|
|
||||||
|
byte[] checksum = new byte[digest.getDigestSize()];
|
||||||
|
|
||||||
|
digest.update(bytes, 0, bytes.length);
|
||||||
|
digest.doFinal(checksum, 0);
|
||||||
|
|
||||||
|
return generateSignatureForHash(privateKey, secureRandom, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will use the bytes AS THE HASHED VALUE to calculate the signature.
|
||||||
|
* <p/>
|
||||||
|
* The returned signature is the {r,s} signature array.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
BigInteger[] generateSignatureForHash(ECPrivateKeyParameters privateKey, SecureRandom secureRandom, byte[] hashBytes) {
|
||||||
|
|
||||||
|
ParametersWithRandom param = new ParametersWithRandom(privateKey, secureRandom);
|
||||||
|
|
||||||
|
ECDSASigner ecdsa = new ECDSASigner();
|
||||||
|
ecdsa.init(true, param);
|
||||||
|
|
||||||
|
return ecdsa.generateSignature(hashBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message will have the (digestName) hash calculated and used for the signature.
|
||||||
|
*
|
||||||
|
* @param signature
|
||||||
|
* is the {r,s} signature array.
|
||||||
|
*
|
||||||
|
* @return true if the signature is valid
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean verifySignature(String digestName, ECPublicKeyParameters publicKey, byte[] message, BigInteger[] signature) {
|
||||||
|
|
||||||
|
Digest digest = DigestFactory.getDigest(digestName);
|
||||||
|
|
||||||
|
byte[] checksum = new byte[digest.getDigestSize()];
|
||||||
|
|
||||||
|
digest.update(message, 0, message.length);
|
||||||
|
digest.doFinal(checksum, 0);
|
||||||
|
|
||||||
|
|
||||||
|
return verifySignatureHash(publicKey, checksum, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The provided hash will be used in the signature verification.
|
||||||
|
*
|
||||||
|
* @param signature
|
||||||
|
* is the {r,s} signature array.
|
||||||
|
*
|
||||||
|
* @return true if the signature is valid
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean verifySignatureHash(ECPublicKeyParameters publicKey, byte[] hash, BigInteger[] signature) {
|
||||||
|
|
||||||
|
ECDSASigner ecdsa = new ECDSASigner();
|
||||||
|
ecdsa.init(false, publicKey);
|
||||||
|
|
||||||
|
|
||||||
|
return ecdsa.verifySignature(hash, signature[0], signature[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
CryptoECC() {
|
||||||
|
}
|
||||||
|
}
|
295
Dorkbox-Util/src/dorkbox/util/crypto/CryptoRSA.java
Normal file
295
Dorkbox-Util/src/dorkbox/util/crypto/CryptoRSA.java
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.Digest;
|
||||||
|
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
|
||||||
|
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.signers.PSSSigner;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is here just for keeping track of how this is done. This should NOT be used, and instead use ECC crypto.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Deprecated
|
||||||
|
public final
|
||||||
|
class CryptoRSA {
|
||||||
|
public static
|
||||||
|
AsymmetricCipherKeyPair generateKeyPair(SecureRandom secureRandom, int keyLength) {
|
||||||
|
RSAKeyPairGenerator keyGen = new RSAKeyPairGenerator();
|
||||||
|
RSAKeyGenerationParameters params = new RSAKeyGenerationParameters(new BigInteger("65537"), // public exponent
|
||||||
|
secureRandom, //pnrg
|
||||||
|
keyLength, // key length
|
||||||
|
8); //the number of iterations of the Miller-Rabin primality test.
|
||||||
|
keyGen.init(params);
|
||||||
|
return keyGen.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA encrypt using public key A, and sign data with private key B.
|
||||||
|
* <p/>
|
||||||
|
* byte[0][] = encrypted data byte[1][] = signature
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[][] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[][] encryptAndSign(AsymmetricBlockCipher rsaEngine,
|
||||||
|
Digest digest,
|
||||||
|
RSAKeyParameters rsaPublicKeyA,
|
||||||
|
RSAPrivateCrtKeyParameters rsaPrivateKeyB,
|
||||||
|
byte[] bytes,
|
||||||
|
Logger logger) {
|
||||||
|
if (bytes.length == 0) {
|
||||||
|
return new byte[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encryptBytes = encrypt(rsaEngine, rsaPublicKeyA, bytes, logger);
|
||||||
|
|
||||||
|
if (encryptBytes.length == 0) {
|
||||||
|
return new byte[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// now sign it.
|
||||||
|
PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
|
||||||
|
|
||||||
|
byte[] signatureRSA = CryptoRSA.sign(signer, rsaPrivateKeyB, encryptBytes, logger);
|
||||||
|
|
||||||
|
if (signatureRSA.length == 0) {
|
||||||
|
return new byte[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[][] total = new byte[2][];
|
||||||
|
total[0] = encryptBytes;
|
||||||
|
total[1] = signatureRSA;
|
||||||
|
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA verify data with public key B, and decrypt using private key A.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] decryptAndVerify(AsymmetricBlockCipher rsaEngine,
|
||||||
|
Digest digest,
|
||||||
|
RSAKeyParameters rsaPublicKeyA,
|
||||||
|
RSAPrivateCrtKeyParameters rsaPrivateKeyB,
|
||||||
|
byte[] encryptedData,
|
||||||
|
byte[] signature,
|
||||||
|
Logger logger) {
|
||||||
|
if (encryptedData.length == 0 || signature.length == 0) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify encrypted data.
|
||||||
|
PSSSigner signer = new PSSSigner(rsaEngine, digest, digest.getDigestSize());
|
||||||
|
|
||||||
|
boolean verify = verify(signer, rsaPublicKeyA, signature, encryptedData);
|
||||||
|
if (!verify) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypt(rsaEngine, rsaPrivateKeyB, encryptedData, logger);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA encrypts data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] encrypt(AsymmetricBlockCipher rsaEngine, RSAKeyParameters rsaPublicKey, byte[] bytes, Logger logger) {
|
||||||
|
rsaEngine.init(true, rsaPublicKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int inputBlockSize = rsaEngine.getInputBlockSize();
|
||||||
|
if (inputBlockSize < bytes.length) {
|
||||||
|
int outSize = rsaEngine.getOutputBlockSize();
|
||||||
|
//noinspection NumericCastThatLosesPrecision
|
||||||
|
int realsize = (int) Math.round(bytes.length / (outSize * 1.0D) + 0.5);
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocateDirect(outSize * realsize);
|
||||||
|
|
||||||
|
int position = 0;
|
||||||
|
|
||||||
|
while (position < bytes.length) {
|
||||||
|
int size = Math.min(inputBlockSize, bytes.length - position);
|
||||||
|
|
||||||
|
byte[] block = rsaEngine.processBlock(bytes, position, size);
|
||||||
|
buffer.put(block, 0, block.length);
|
||||||
|
|
||||||
|
position += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return buffer.array();
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return rsaEngine.processBlock(bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform RSA cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA decrypt data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] decrypt(AsymmetricBlockCipher rsaEngine, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] bytes, Logger logger) {
|
||||||
|
rsaEngine.init(false, rsaPrivateKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int inputBlockSize = rsaEngine.getInputBlockSize();
|
||||||
|
if (inputBlockSize < bytes.length) {
|
||||||
|
int outSize = rsaEngine.getOutputBlockSize();
|
||||||
|
//noinspection NumericCastThatLosesPrecision
|
||||||
|
int realsize = (int) Math.round(bytes.length / (outSize * 1.0D) + 0.5);
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream(outSize * realsize);
|
||||||
|
|
||||||
|
int position = 0;
|
||||||
|
|
||||||
|
while (position < bytes.length) {
|
||||||
|
int size = Math.min(inputBlockSize, bytes.length - position);
|
||||||
|
|
||||||
|
byte[] block = rsaEngine.processBlock(bytes, position, size);
|
||||||
|
buffer.write(block, 0, block.length);
|
||||||
|
|
||||||
|
position += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return buffer.toByteArray();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return rsaEngine.processBlock(bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform RSA cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA sign data with a specified key.
|
||||||
|
*
|
||||||
|
* @param logger
|
||||||
|
* may be null, if no log output is necessary
|
||||||
|
*
|
||||||
|
* @return empty byte[] if error
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] sign(PSSSigner signer, RSAPrivateCrtKeyParameters rsaPrivateKey, byte[] mesg, Logger logger) {
|
||||||
|
signer.init(true, rsaPrivateKey);
|
||||||
|
signer.update(mesg, 0, mesg.length);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return signer.generateSignature();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger != null) {
|
||||||
|
logger.error("Unable to perform RSA cipher.", e);
|
||||||
|
}
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA verify data with a specified key.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean verify(PSSSigner signer, RSAKeyParameters rsaPublicKey, byte[] sig, byte[] mesg) {
|
||||||
|
signer.init(false, rsaPublicKey);
|
||||||
|
signer.update(mesg, 0, mesg.length);
|
||||||
|
|
||||||
|
return signer.verifySignature(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
public static
|
||||||
|
boolean compare(RSAKeyParameters publicA, RSAKeyParameters publicB) {
|
||||||
|
if (!publicA.getExponent()
|
||||||
|
.equals(publicB.getExponent())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!publicA.getModulus()
|
||||||
|
.equals(publicB.getModulus())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
public static
|
||||||
|
boolean compare(RSAPrivateCrtKeyParameters private1, RSAPrivateCrtKeyParameters private2) {
|
||||||
|
if (!private1.getModulus()
|
||||||
|
.equals(private2.getModulus())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getExponent()
|
||||||
|
.equals(private2.getExponent())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getDP()
|
||||||
|
.equals(private2.getDP())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getDQ()
|
||||||
|
.equals(private2.getDQ())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getP()
|
||||||
|
.equals(private2.getP())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getPublicExponent()
|
||||||
|
.equals(private2.getPublicExponent())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getQ()
|
||||||
|
.equals(private2.getQ())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!private1.getQInv()
|
||||||
|
.equals(private2.getQInv())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private
|
||||||
|
CryptoRSA() {
|
||||||
|
}
|
||||||
|
}
|
221
Dorkbox-Util/src/dorkbox/util/crypto/CryptoSCrypt.java
Normal file
221
Dorkbox-Util/src/dorkbox/util/crypto/CryptoSCrypt.java
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
package dorkbox.util.crypto;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the <a href="http://www.tarsnap.com/scrypt/scrypt.pdf"/>scrypt</a> key derivation function.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final
|
||||||
|
class CryptoSCrypt {
|
||||||
|
/**
|
||||||
|
* Hash the supplied plaintext password and generate output using default parameters
|
||||||
|
* <p/>
|
||||||
|
* The password chars are no longer valid after this call
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* Password.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String encrypt(char[] password) {
|
||||||
|
return encrypt(password, 16384, 32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the supplied plaintext password and generate output using default parameters
|
||||||
|
* <p/>
|
||||||
|
* The password chars are no longer valid after this call
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* Password.
|
||||||
|
* @param salt
|
||||||
|
* Salt parameter
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String encrypt(char[] password, byte[] salt) {
|
||||||
|
return encrypt(password, salt, 16384, 32, 1, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the supplied plaintext password and generate output.
|
||||||
|
* <p/>
|
||||||
|
* The password chars are no longer valid after this call
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* Password.
|
||||||
|
* @param N
|
||||||
|
* CPU cost parameter.
|
||||||
|
* @param r
|
||||||
|
* Memory cost parameter.
|
||||||
|
* @param p
|
||||||
|
* Parallelization parameter.
|
||||||
|
*
|
||||||
|
* @return The hashed password.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String encrypt(char[] password, int N, int r, int p) {
|
||||||
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
|
byte[] salt = new byte[32];
|
||||||
|
secureRandom.nextBytes(salt);
|
||||||
|
|
||||||
|
return encrypt(password, salt, N, r, p, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash the supplied plaintext password and generate output.
|
||||||
|
* <p/>
|
||||||
|
* The password chars are no longer valid after this call
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* Password.
|
||||||
|
* @param salt
|
||||||
|
* Salt parameter
|
||||||
|
* @param N
|
||||||
|
* CPU cost parameter.
|
||||||
|
* @param r
|
||||||
|
* Memory cost parameter.
|
||||||
|
* @param p
|
||||||
|
* Parallelization parameter.
|
||||||
|
* @param dkLen
|
||||||
|
* Intended length of the derived key.
|
||||||
|
*
|
||||||
|
* @return The hashed password.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String encrypt(char[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||||
|
// Note: this saves the char array in UTF-16 format of bytes.
|
||||||
|
// can't use password after this as it's been changed to '*'
|
||||||
|
byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
|
||||||
|
|
||||||
|
byte[] derived = encrypt(passwordBytes, salt, N, r, p, dkLen);
|
||||||
|
|
||||||
|
String params = Integer.toString(log2(N) << 16 | r << 8 | p, 16);
|
||||||
|
|
||||||
|
@SuppressWarnings("StringBufferReplaceableByString")
|
||||||
|
StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2);
|
||||||
|
sb.append("$s0$")
|
||||||
|
.append(params)
|
||||||
|
.append('$');
|
||||||
|
sb.append(dorkbox.util.Base64Fast.encodeToString(salt, false))
|
||||||
|
.append('$');
|
||||||
|
sb.append(dorkbox.util.Base64Fast.encodeToString(derived, false));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the supplied plaintext password to a hashed password.
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* Plaintext password.
|
||||||
|
* @param hashed
|
||||||
|
* scrypt hashed password.
|
||||||
|
*
|
||||||
|
* @return true if password matches hashed value.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
boolean verify(char[] password, String hashed) {
|
||||||
|
// Note: this saves the char array in UTF-16 format of bytes.
|
||||||
|
// can't use password after this as it's been changed to '*'
|
||||||
|
byte[] passwordBytes = Crypto.Util.charToBytesPassword(password);
|
||||||
|
|
||||||
|
String[] parts = hashed.split("\\$");
|
||||||
|
|
||||||
|
if (parts.length != 5 || !parts[1].equals("s0")) {
|
||||||
|
throw new IllegalArgumentException("Invalid hashed value");
|
||||||
|
}
|
||||||
|
|
||||||
|
int params = Integer.parseInt(parts[2], 16);
|
||||||
|
byte[] salt = dorkbox.util.Base64Fast.decodeFast(parts[3]);
|
||||||
|
byte[] derived0 = dorkbox.util.Base64Fast.decodeFast(parts[4]);
|
||||||
|
|
||||||
|
//noinspection NumericCastThatLosesPrecision
|
||||||
|
int N = (int) Math.pow(2, params >> 16 & 0xFF);
|
||||||
|
int r = params >> 8 & 0xFF;
|
||||||
|
int p = params & 0xFF;
|
||||||
|
|
||||||
|
int length = derived0.length;
|
||||||
|
if (length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] derived1 = encrypt(passwordBytes, salt, N, r, p, length);
|
||||||
|
|
||||||
|
if (length != derived1.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
result |= derived0[i] ^ derived1[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
int log2(int n) {
|
||||||
|
int log = 0;
|
||||||
|
if ((n & 0xFFFF0000) != 0) {
|
||||||
|
n >>>= 16;
|
||||||
|
log = 16;
|
||||||
|
}
|
||||||
|
if (n >= 256) {
|
||||||
|
n >>>= 8;
|
||||||
|
log += 8;
|
||||||
|
}
|
||||||
|
if (n >= 16) {
|
||||||
|
n >>>= 4;
|
||||||
|
log += 4;
|
||||||
|
}
|
||||||
|
if (n >= 4) {
|
||||||
|
n >>>= 2;
|
||||||
|
log += 2;
|
||||||
|
}
|
||||||
|
return log + (n >>> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure Java implementation of the <a href="http://www.tarsnap.com/scrypt/scrypt.pdf"/>scrypt KDF</a>.
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* Password.
|
||||||
|
* @param salt
|
||||||
|
* Salt.
|
||||||
|
* @param N
|
||||||
|
* CPU cost parameter.
|
||||||
|
* @param r
|
||||||
|
* Memory cost parameter.
|
||||||
|
* @param p
|
||||||
|
* Parallelization parameter.
|
||||||
|
* @param dkLen
|
||||||
|
* Intended length of the derived key.
|
||||||
|
*
|
||||||
|
* @return The derived key.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
byte[] encrypt(byte[] password, byte[] salt, int N, int r, int p, int dkLen) {
|
||||||
|
if (N == 0 || (N & N - 1) != 0) {
|
||||||
|
throw new IllegalArgumentException("N must be > 0 and a power of 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (N > Integer.MAX_VALUE / 128 / r) {
|
||||||
|
throw new IllegalArgumentException("Parameter N is too large");
|
||||||
|
}
|
||||||
|
if (r > Integer.MAX_VALUE / 128 / p) {
|
||||||
|
throw new IllegalArgumentException("Parameter r is too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return org.bouncycastle.crypto.generators.SCrypt.generate(password, salt, N, r, p, dkLen);
|
||||||
|
} finally {
|
||||||
|
// now zero out the bytes in password.
|
||||||
|
Arrays.fill(password, (byte) 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
CryptoSCrypt() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user