package cologne.eck.peafactory.crypto;
/*
* Peafactory - Production of Password Encryption Archives
* Copyright (C) 2015 Axel von dem Bruch
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* See: http://www.gnu.org/licenses/gpl-2.0.html
* You should have received a copy of the GNU General Public License
* along with this library.
*/
/**
* Performs the encryption/decryption of byte blocks
* ECB mode for session key and key handling.
* File encryption is done in the mode class.
*/
import javax.swing.JOptionPane;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.modes.SICBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import cologne.eck.all_peas.data.Attachments;
import cologne.eck.all_peas.data.PeaProperties;
import cologne.eck.peafactory.crypto.kdf.KeyDerivation;
import cologne.eck.tools.Comparator;
import cologne.eck.tools.Zeroizer;
public class CipherStuff {
private static CipherStuff cipherStuff = new CipherStuff();
private static BlockCipher cipherAlgo;
private static AuthenticatedEncryption authenticatedEncryption = new EAXMode();
private static String errorMessage = null;
private static SessionKeyCrypt skc = null;
// returns required length of key in bytes
public final static int getKeySize() {
int keySize = 0;
if (cipherAlgo == null) {
System.err.println("CipherStuff getKeySize: cipherAlgo null");
System.exit(1);
}
if (cipherAlgo.getAlgorithmName().startsWith("Threefish") ) {
// key size = block size for Threefish 256/512/1024
keySize = cipherAlgo.getBlockSize();
} else if ( cipherAlgo.getAlgorithmName().equals("Twofish" )) {
keySize = 32; // Bytes
} else if (cipherAlgo.getAlgorithmName().equals("Serpent") ) {
//System.out.println("SerpentEngine");
keySize = 32; // Bytes
} else if (cipherAlgo.getAlgorithmName().equals("AES") ) {
keySize = 32; // Bytes
} else if (cipherAlgo.getAlgorithmName().equals("AESFast") ) {
keySize = 32; // Bytes
} else if (cipherAlgo.getAlgorithmName().equals("Shacal2") ) {
keySize = 64; // Bytes
} else {
System.err.println("CipherStuff getKeySize: invalid Algorithm");
System.exit(1);
}
return keySize;
}
/**
* Encrypt/decrypt an array of bytes. This function is only used to
* encrypt the session key
*
* @param forEncryption - true for encryption, false for decryption
* @param input - plain text or cipher text
* @param key - the cryptographic key for the cipher
* @param zeroize - fills the key with 0 for true (when the key is no longer used)
* @return - plain text or cipher text
*/
public final static byte[] processCTR(boolean forEncryption, byte[] input, byte[] key,
boolean zeroize) {
// check for program bug
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
KeyParameter kp = new KeyParameter(key);
if (zeroize == true) {
Zeroizer.zero(key);
}
byte[] iv = null;
if (forEncryption == false) {
// input is ciphertext, IV is stored on top of ciphertext
// get IV:
iv = new byte[CipherStuff.getCipherAlgo().getBlockSize()];
System.arraycopy(input, 0, iv, 0, iv.length);
// truncate ciphertext:
byte[] tmp = new byte[input.length - iv.length];
System.arraycopy(input, iv.length, tmp, 0, tmp.length);
input = tmp;
} else {
// input is plaintext
iv = new RandomStuff().createRandomBytes(CipherStuff.getCipherAlgo().getBlockSize());
}
ParametersWithIV ivp = new ParametersWithIV(kp, iv);
BufferedBlockCipher b = new BufferedBlockCipher(new SICBlockCipher(cipherAlgo));
b.init(true, ivp);
byte[] out = new byte[input.length];
int len = b.processBytes(input, 0, input.length, out, 0);
try {
len += b.doFinal(out, len);
} catch (DataLengthException e) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
} catch (IllegalStateException e) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
} catch (InvalidCipherTextException e) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
}
Zeroizer.zero(kp.getKey());
if (forEncryption == false) {// decryption: output plaintext
return out;
} else { // encryption: output (IV || ciphertext)
Zeroizer.zero(input);
// store IV on top of ciphertext
byte[] tmp = new byte[out.length + iv.length];
System.arraycopy(iv, 0, tmp, 0, iv.length);
System.arraycopy(out, 0, tmp, iv.length, out.length);
return tmp;
}
}
/**
* Encrypt an array of bytes.
*
* @param plainBytes the plain text to encrypt
* @param keyMaterial the derived key or null (there
* is a session key available)
* @param encryptBySessionKey if true, the derived key is encrypted,
* otherwise the key is cleared
* @return the cipher text
*/
public final byte[] encrypt(byte[] plainBytes, byte[] keyMaterial,
boolean encryptBySessionKey){
//System.out.println("CipherStuff, encrypt - encryptBySessionKey: " + encryptBySessionKey);
//Help.printBytes("keyMaterial", keyMaterial);
//Help.printlastBytes("plainBytes", plainBytes, 64);
checkSessionKeyCrypt();
// Check if plainBytes is too long to be loaded in memory:
if (plainBytes.length > 8192 * 64 * 16) { // fileBlockSize = 8192 * 64 * 16
// check free memory to avoid swapping:
System.gc(); // this might not work...
long freeMemory = Runtime.getRuntime().maxMemory()
- (Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory());
//System.out.println("Free memory: " + freeMemory);
if (plainBytes.length <= freeMemory) {
System.out.println("Warning: long plain text");
} else {
JOptionPane.showMessageDialog(null,
"The content you want to encrypt is too large \n"
+ "to encrypt in one block on your system: \n"
+ plainBytes.length + "\n\n"
+ "Use the file encrytion pea instead. \n"
+ "(If you blew up an existing text based pea, \n"
+ "you have to save the content by copy-and-paste).",
"Memory error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
// unique for each encryption
byte[] iv = Attachments.generateNonce();
// if nonce was used and filled before...
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(iv);
}
//Help.printBytes("CipherStuff encrypt iv/nonce", iv);
byte[] key = detectKey(keyMaterial);
//Help.printBytes("CipherStuff encrypt key", key);
//
//------ENCRYPTION:
//
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
// contains the MAC:
byte[] cipherBytes = authenticatedEncryption.processBytes(true, plainBytes, key, iv);
Zeroizer.zero(plainBytes);
//Help.printlastBytes("CipherStuff encrypt cipherBytes + MAC", cipherBytes, 64);
// add pswIdentifier to check password in decryption before check of Mac
byte[] pswIdentifierAttachment = null;
try {
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
pswIdentifierAttachment = Attachments.createPswIdentifierAttachment(key, iv);
} catch (IllegalStateException e) {
errorMessage = "###Unexpected error by encrypting pswIdentifier: Illegal state of cipher";
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
return null;
} catch (InvalidCipherTextException e) {
errorMessage = "###Unexpected error by encrypting pswIdentifier: Invalid cipher text";
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println(e.toString() + " - " + e.getMessage());
e.printStackTrace();
}
return null;
}
//Help.printBytes("CipherStuff encrypt: pswIdentifierAttachment", pswIdentifierAttachment);
//Help.printBytes("CipherStuff encrypt: iv", iv);
//Help.printBytes("CipherStuff encrypt: key", key);
cipherBytes = Attachments.addBytes(cipherBytes, pswIdentifierAttachment);
//Help.printBytes("CipherStuff encrypt: pswIdentifierAttachment", pswIdentifierAttachment);
//Help.printlastBytes("CipherStuff encrypt: cipherBytes + pswIdentifierAttachment", cipherBytes, 64);
// prepare to save:
cipherBytes = Attachments.addNonce(cipherBytes, iv);
//Help.printlastBytes("CipherStuff encrypt: cipherBytes + nonce", cipherBytes, 64);
cipherBytes = Attachments.attachBytes(cipherBytes, KeyDerivation.getSalt());
//Help.printBytes("CipherStuff encrypt: attachedSalt", KeyDerivation.getAttachedSalt());
//Help.printlastBytes("CipherStuff encrypt: cipherBytes + attachedSalt", cipherBytes, 64);
cipherBytes = Attachments.addFileIdentifier(cipherBytes);
//Help.printBytes("CipherStuff encrypt: fileIdentifier", Attachments.getFileIdentifier());
//Help.printlastBytes("CipherStuff encrypt: cipherBytes + fileIdentifier", cipherBytes, 64);
//
// handle the keys
//
handleKey(key, encryptBySessionKey);
return cipherBytes;
}
/**
* Encrypt the derived key and use as session key
*
* @param keyMaterial the derived key
*/
public final void encryptKeyFromKeyMaterial(byte[] keyMaterial) {
checkSessionKeyCrypt();
if (keyMaterial == null) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println("Error: new keyMaterial null (CryptStuff.encryptKeyFromKeyMaterial");
Thread.currentThread().getStackTrace();
}
System.err.println("Fatal error: CipherStukk encryptKeyFromKeyMaterial");
System.exit(1);
} else {
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(keyMaterial);
}
}
byte[] key = detectKey(keyMaterial);
handleKey(key, true);
}
/**
* Decrypt an array of bytes.
*
* @param cipherText the cipher text to decrypt
* @param keyMaterial the derived key or null if
* there is a session key
* @param encryptBySessionKey encrypt the key or clear it
* @return the plain text or null if the
* decryption failed
*/
public final byte[] decrypt(byte[] cipherText, byte[] keyMaterial,
boolean encryptBySessionKey) {
//System.out.println("CipherStuff, decrypt encryptBySessionKey: " + encryptBySessionKey);
//Help.printlastBytes("cipherText", cipherText, 64);
//if (keyMaterial != null) Help.printBytes("keyMaterial", keyMaterial);
checkSessionKeyCrypt();
// check
if (cipherText == null) {
errorMessage = "no_ciphertext";
return null;
}
//Help.printBytes("CipherStuff decrypt: fileIdentifier", Attachments.getFileIdentifier());
cipherText = Attachments.checkFileIdentifier(cipherText, true);
//Help.printlastBytes("CipherStuff decrypt: - fileIdentifier", cipherText, 64);
if (cipherText == null) { // check returns null if failed
errorMessage = "unsuitable_ciphertext_id";
return null;
}
cipherText = Attachments.cutSalt(cipherText);
//Help.printBytes("CipherStuff decrypt: attachedSalt", KeyDerivation.getAttachedSalt());
//Help.printlastBytes("CipherStuff decrypt: - attachedSalt", cipherText, 64);
byte[] iv = Attachments.calculateNonce(cipherText);
if (iv == null) {
errorMessage = "unsuitable_ciphertext_len";
return null;
} else {
cipherText = Attachments.cutNonce(cipherText);
}
//Help.printlastBytes("CipherStuff decrypt: - nonce", cipherText, 64);
// Check if plainBytes is too long to be loaded in memory:
if (cipherText.length > 8192 * 64 * 16) { // fileBlockSize = 8192 * 64 * 16
// get free memory to avoid swapping:
System.gc(); // this might not work...
long freeMemory = Runtime.getRuntime().maxMemory()
- (Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory());
//System.out.println("Free memory: " + freeMemory);
if (cipherText.length <= freeMemory) {
System.out.println("Warning: long plain text");
if (cipherText.length > (freeMemory - 8192 * 64)){
JOptionPane.showMessageDialog(null,
"The content you want to decrypt is already large: \n"
+ cipherText.length + "\n\n"
+ "Adding more content may cause a memory error. \n",
"Memory warning",
JOptionPane.WARNING_MESSAGE);
}
} else {
JOptionPane.showMessageDialog(null,
"The content you want to decrypt is too large \n"
+ "to decrypt in one block on your system: \n"
+ cipherText.length + "\n\n"
+ "It was probably encrypted on a system with more memory. \n"
+ "You have to decrypt it on another system... \n",
"Memory error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
byte[] key = detectKey(keyMaterial);
//Help.printBytes("CipherStuff decrypt: key", key);
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
byte[] pswIdentifierAttachment = new byte[Attachments.getPswIdentifierSize()];
System.arraycopy(cipherText,
cipherText.length - pswIdentifierAttachment.length,
pswIdentifierAttachment,
0,
pswIdentifierAttachment.length);
//Help.printBytes("CipherStuff decrypt: pswIdentifierAttachment", pswIdentifierAttachment);
//Help.printBytes("CipherStuff decrypt: iv", iv);
//Help.printBytes("CipherStuff decrypt: key", key);
if (Attachments.checkPswIdentifier(pswIdentifierAttachment, key, iv) == false) {
System.out.println("Password identifier failed.");
errorMessage = "password_failed";
return null;
} else {
// cut pswIdentifier
byte[] tmp = new byte[cipherText.length - pswIdentifierAttachment.length];
System.arraycopy(cipherText, 0, tmp, 0, tmp.length);
cipherText = tmp;
}
int macLength = CipherStuff.getCipherAlgo().getBlockSize();
// check mac length:
if (cipherText.length < macLength) {
errorMessage = "unsuitable_ciphertext_mac";
return null;
}
byte[] plainText = authenticatedEncryption.processBytes(false, cipherText, key, iv);
//
// handle the keys
//
handleKey(key, encryptBySessionKey);
return plainText;
}
/**
* Get the key from keyMaterial or the session key
*
* @param keyMaterial the derived key or null
* @return the key
*/
protected final byte[] detectKey(byte[] keyMaterial) {
byte[] key = null;
if (keyMaterial != null) { // use as key
//Help.printBytes("CipherStuff detectKey\n keyMaterial: ",keyMaterial);
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(keyMaterial);
}
// check size:
int keySize = CipherStuff.getKeySize();
if (keyMaterial.length != keySize) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println("Invalid key size");
Thread.currentThread().getStackTrace();
}
System.err.println("CryptStuff detectKey: invalid size of keyMaterial");
System.exit(1);
} else {
key = keyMaterial;
}
} else { // keyMaterial == null: decrypt encryptedKey to get key
if (skc == null) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println("SessionKeyCrypt null");
Thread.currentThread().getStackTrace();
}
System.err.println("SessionKeyCrypt null");
throw new IllegalStateException("SessionKeyCrypt not initialized");
} else {
key = skc.getKey();
}
//Help.printBytes("CipherStuff detectKey\n key: ",key);
}
return key;
}
/**
* Encrypt or clear the key
*
* @param key the key
* @param encryptSessionKey if true, encrypt the key, if
* false clear it
*/
protected final void handleKey(byte[] key, boolean encryptSessionKey) {
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(key);
}
if (encryptSessionKey == false) {
Zeroizer.zero(key);
} else {
if (skc == null) {
skc = new SessionKeyCrypt();
}
skc.storeKey(key);
}
}
private final void checkSessionKeyCrypt(){
if (skc == null) {
skc = new SessionKeyCrypt();
}
}
//=========================
// Getter & Setter:
public final static void setCipherAlgo (BlockCipher _cipherAlgo) {
cipherAlgo = _cipherAlgo;
}
public final static BlockCipher getCipherAlgo () {
return cipherAlgo;
}
public final static void setCipherMode (AuthenticatedEncryption _cipherMode) {
authenticatedEncryption = _cipherMode;
}
public final static AuthenticatedEncryption getCipherMode(){
return authenticatedEncryption;
}
public final SessionKeyCrypt getSessionKeyCrypt(){
if (skc == null) {
skc = new SessionKeyCrypt();
}
return skc;
}
// used in FileControl for initialization
public final static void setSessionKeyCrypt(SessionKeyCrypt _skc){
if (skc != null){
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println("SessionKeyCrypt setSessionKeyCrypt: two instances");
Thread.currentThread().getStackTrace();
}
new IllegalStateException("two Instances of SessionKeyCrypt");
}
skc = _skc;
}
public final static String getErrorMessage() {
return errorMessage;
}
public final static void setErrorMessage(String _errorMessage) {
errorMessage = _errorMessage;
}
/**
* @return the cipherStuff
*/
public final static CipherStuff getInstance() {
if(cipherStuff == null){
cipherStuff = new CipherStuff();
}
return cipherStuff;
}
}