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