/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.store.saltedhash;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import freenet.crypt.BlockCipher;
import freenet.crypt.PCFBMode;
import freenet.crypt.SHA256;
import freenet.crypt.UnsupportedCipherException;
import freenet.crypt.ciphers.Rijndael;
import freenet.node.MasterKeys;
import freenet.support.ByteArrayWrapper;
import freenet.support.Logger;
/**
* Cipher Manager
*
* Manage all kind of digestion and encryption in store
*
* @author sdiz
*/
public class CipherManager {
/**
* The actual salt. 16 bytes.
*/
private byte[] salt;
/**
* The original on-disk salt, may be encrypted. 16 bytes.
*/
private byte[] diskSalt;
CipherManager(byte[] salt, byte[] diskSalt) {
assert salt.length == 0x10;
this.salt = salt;
this.diskSalt = diskSalt;
}
/**
* Get salt
*
* @return salt
*/
byte[] getDiskSalt() {
return diskSalt;
}
/**
* Cache for digested keys
*/
@SuppressWarnings("serial")
private Map<ByteArrayWrapper, byte[]> digestRoutingKeyCache = new LinkedHashMap<ByteArrayWrapper, byte[]>() {
@Override
protected boolean removeEldestEntry(Map.Entry<ByteArrayWrapper, byte[]> eldest) {
return size() > 128;
}
};
/**
* Get digested routing key
*
* @param plainKey
* @return
*/
byte[] getDigestedKey(byte[] plainKey) {
ByteArrayWrapper key = new ByteArrayWrapper(plainKey);
synchronized (digestRoutingKeyCache) {
byte[] dk = digestRoutingKeyCache.get(key);
if (dk != null)
return dk;
}
MessageDigest digest = SHA256.getMessageDigest();
try {
digest.update(plainKey);
digest.update(salt);
byte[] hashedRoutingKey = digest.digest();
assert hashedRoutingKey.length == 0x20;
synchronized (digestRoutingKeyCache) {
digestRoutingKeyCache.put(key, hashedRoutingKey);
}
return hashedRoutingKey;
} finally {
SHA256.returnMessageDigest(digest);
}
}
/**
* Encrypt this entry
*/
void encrypt(SaltedHashFreenetStore.Entry entry, Random random) {
if (entry.isEncrypted)
return;
entry.dataEncryptIV = new byte[16];
random.nextBytes(entry.dataEncryptIV);
PCFBMode cipher = makeCipher(entry.dataEncryptIV, entry.plainRoutingKey);
cipher.blockEncipher(entry.header, 0, entry.header.length);
cipher.blockEncipher(entry.data, 0, entry.data.length);
entry.getDigestedRoutingKey();
entry.isEncrypted = true;
}
/**
* Verify and decrypt this entry
*
* @param routingKey
* @return <code>true</code> if the <code>routeKey</code> match and the entry is decrypted.
*/
boolean decrypt(SaltedHashFreenetStore.Entry entry, byte[] routingKey) {
assert entry.header != null;
assert entry.data != null;
if (!entry.isEncrypted) {
// Already decrypted
if (Arrays.equals(entry.plainRoutingKey, routingKey))
return true;
else
return false;
}
if (entry.plainRoutingKey != null) {
// we knew the key
if (!Arrays.equals(entry.plainRoutingKey, routingKey)) {
return false;
}
} else {
// we do not know the plain key, let's check the digest
if (!Arrays.equals(entry.digestedRoutingKey, getDigestedKey(routingKey)))
return false;
}
entry.plainRoutingKey = routingKey;
PCFBMode cipher = makeCipher(entry.dataEncryptIV, entry.plainRoutingKey);
cipher.blockDecipher(entry.header, 0, entry.header.length);
cipher.blockDecipher(entry.data, 0, entry.data.length);
entry.isEncrypted = false;
return true;
}
/**
* Create PCFBMode object for this key
*/
PCFBMode makeCipher(byte[] iv, byte[] key) {
byte[] iv2 = new byte[0x20]; // 256 bits
System.arraycopy(salt, 0, iv2, 0, 0x10);
System.arraycopy(iv, 0, iv2, 0x10, 0x10);
try {
BlockCipher aes = new Rijndael(256, 256);
aes.initialize(key);
return PCFBMode.create(aes, iv2);
} catch (UnsupportedCipherException e) {
Logger.error(this, "Rijndael not supported!", e);
throw new Error("Rijndael not supported!", e);
}
}
public void shutdown() {
MasterKeys.clear(salt);
MasterKeys.clear(diskSalt);
}
}