/*******************************************************************************
* Copyright (c) 2005-2011, G. Weirich and Elexis
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* G. Weirich - initial implementation
*
*******************************************************************************/
package ch.rgw.crypt;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import ch.rgw.tools.ExHandler;
import ch.rgw.tools.Result;
import ch.rgw.tools.TimeTool;
public class JCECrypter implements Cryptologist {
private static final String KEY_ALGO = "AES";
// private static final String SIGNATURE_ALGO = "SHA1withRSA";
private static final String SIGNATURE_ALGO = "SHA512withRSA";
private static final String SYMM_CIPHER_ALGO = "Blowfish";
private static final String RSA_ALGO = "RSA/ECB/PKCS1Padding";
public static short VERSION = 0x0102;
public static short MAGIC = (short) 0xefde;
public static short KEY_MARKER = 0x10;
public static short IV_MARKER = 0x20;
public static short DATA_MARKER = 0x30;
protected JCEKeyManager km;
protected String userKey;
protected char[] pwd;
/**
* Create a new Crypter. If the named keystore does not exist, it well created newly and a key
* for the named user will be created as well.
*
* @param keystore
* keystore to use or NULL for default keystore
* @param kspwd
* keystore password or NULL for default password
* @param mykey
* identifier for user's key in the named keystore
* @param keypwd
* password for the user's key
* @throws Exception
*/
public JCECrypter(String keystore, char[] kspwd, String mykey, char[] keypwd) throws Exception{
this(kspwd, mykey, keypwd);
if (keystore == null) {
keystore = System.getProperty("user.home") + File.separator + ".JCECrypter";
if (kspwd == null) {
kspwd = "JCECrypterDefault".toCharArray();
}
}
km = new JCEKeyManager(keystore, null, kspwd);
if (km.load(true)) {
if (!km.existsPrivate(mykey)) {
KeyPair kp = km.generateKeys();
X509Certificate cert =
km.generateCertificate(kp.getPublic(), kp.getPrivate(), userKey, userKey, null,
null);
km.addKeyPair(kp.getPrivate(), cert, pwd);
// km.addCertificate(cert);
km.save();
}
} else {
km = null;
}
}
protected JCECrypter(char[] kspwd, String mykey, char[] keypwd){
userKey = mykey;
pwd = keypwd;
// Security
// .addProvider(new
// org.bouncycastle.jce.provider.BouncyCastleProvider()); // Add
}
@Override
protected void finalize() throws Throwable{
if (pwd != null) {
for (int i = 0; i < pwd.length; i++) {
pwd[i] = 0;
}
}
super.finalize();
}
public Result<byte[]> decrypt(byte[] encrypted){
try {
PrivateKey pk = km.getPrivateKey(userKey, pwd);
// Cipher rsaCip = Cipher.getInstance("RSA/None/OAEPPadding", "BC");
Cipher rsaCip = Cipher.getInstance(RSA_ALGO);
rsaCip.init(Cipher.DECRYPT_MODE, pk);
ByteArrayInputStream bais = new ByteArrayInputStream(encrypted);
DataInputStream di = new DataInputStream(bais);
int magic = di.readShort();
if (magic != MAGIC) {
return new Result<byte[]>(Result.SEVERITY.ERROR, 1,
"Bad data format while trying to decrypt", null, true);
}
int version = di.readShort();
int mark = di.readShort();
if (mark != KEY_MARKER) {
return new Result<byte[]>(Result.SEVERITY.ERROR, 2, "unexpected block marker",
null, true);
}
int len = di.readInt();
byte[] d = new byte[len];
di.readFully(d);
Key bfKey = new SecretKeySpec(rsaCip.doFinal(d), SYMM_CIPHER_ALGO);
Cipher aesCip = Cipher.getInstance(SYMM_CIPHER_ALGO);
aesCip.init(Cipher.DECRYPT_MODE, bfKey /* , new IvParameterSpec(iv) */);
mark = di.readShort();
if (mark != DATA_MARKER) {
return new Result<byte[]>(Result.SEVERITY.ERROR, 4, "unexpected block marker",
null, true);
}
len = di.readInt();
d = new byte[len];
di.readFully(d);
return new Result<byte[]>(aesCip.doFinal(d));
} catch (Exception e) {
ExHandler.handle(e);
}
return null;
}
public void decrypt(InputStream source, OutputStream dest) throws CryptologistException{
try {
PrivateKey pk = km.getPrivateKey(userKey, pwd);
// Cipher rsaCip = Cipher.getInstance("RSA/None/OAEPPadding", "BC");
Cipher rsaCip = Cipher.getInstance(RSA_ALGO);
rsaCip.init(Cipher.DECRYPT_MODE, pk);
DataInputStream di = new DataInputStream(source);
int magic = di.readShort();
if (magic != MAGIC) {
throw new CryptologistException("Bad data format while trying to decrypt",
CryptologistException.ERR_BAD_PROTOCOL);
}
int version = di.readShort();
int mark = di.readShort();
if (mark != KEY_MARKER) {
throw new CryptologistException("unexpected block marker",
CryptologistException.ERR_BAD_PROTOCOL);
}
int len = di.readInt();
byte[] d = new byte[len];
di.readFully(d);
Key bfKey = new SecretKeySpec(rsaCip.doFinal(d), SYMM_CIPHER_ALGO);
Cipher aesCip = Cipher.getInstance(SYMM_CIPHER_ALGO);
aesCip.init(Cipher.DECRYPT_MODE, bfKey /* , new IvParameterSpec(iv) */);
while (di.available() > 1) {
mark = di.readShort();
if (mark != DATA_MARKER) {
throw new CryptologistException("unexpected block marker",
CryptologistException.ERR_BAD_PROTOCOL);
}
len = di.readInt();
d = new byte[len];
di.readFully(d);
byte[] dec = aesCip.doFinal(d);
dest.write(dec);
}
dest.flush();
} catch (Exception e) {
throw new CryptologistException("Error while decoding " + e.getMessage(),
CryptologistException.ERR_DECRYPTION_FAILURE);
}
}
public void encrypt(InputStream source, OutputStream dest, String receiverKeyName)
throws CryptologistException{
final int BUFLEN = 65535;
try {
PublicKey cert = km.getPublicKey(receiverKeyName);
Cipher bfCip = Cipher.getInstance(SYMM_CIPHER_ALGO);
byte[] bfKey = generateBlowfishKey();
SecretKeySpec spec = new SecretKeySpec(bfKey, SYMM_CIPHER_ALGO);
bfCip.init(Cipher.ENCRYPT_MODE, spec);
// Cipher rsaCip=Cipher.getInstance("RSA/None/OAEPPadding", "BC");
Cipher rsaCip = Cipher.getInstance(RSA_ALGO);
rsaCip.init(Cipher.ENCRYPT_MODE, cert);
DataOutputStream dao = new DataOutputStream(dest);
dao.writeShort(MAGIC);
dao.writeShort(VERSION);
writeBlock(dao, rsaCip.doFinal(bfKey), KEY_MARKER);
// writeBlock(dao,rsaCip.doFinal(aes_iv),IV_MARKER);
// aesCip.init(Cipher.ENCRYPT_MODE, aesKey,new
// IvParameterSpec(aes_iv));
byte[] buffer = new byte[BUFLEN];
int in;
while ((in = source.read(buffer)) == BUFLEN) {
writeBlock(dao, bfCip.doFinal(buffer), DATA_MARKER);
}
if (in > 0) {
writeBlock(dao, bfCip.doFinal(buffer, 0, in), DATA_MARKER);
}
dao.flush();
} catch (Exception e) {
throw new CryptologistException("Encryption failed: " + e.getMessage(),
CryptologistException.ERR_ENCRYPTION_FAILURE);
}
}
public byte[] encrypt(byte[] source, String receiverKeyName){
try {
PublicKey cert = km.getPublicKey(receiverKeyName);
Cipher bfCip = Cipher.getInstance(SYMM_CIPHER_ALGO);
byte[] bfKey = generateBlowfishKey();
SecretKeySpec spec = new SecretKeySpec(bfKey, SYMM_CIPHER_ALGO);
bfCip.init(Cipher.ENCRYPT_MODE, spec);
// Cipher rsaCip=Cipher.getInstance("RSA/None/OAEPPadding", "BC");
Cipher rsaCip = Cipher.getInstance(RSA_ALGO);
rsaCip.init(Cipher.ENCRYPT_MODE, cert);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dao = new DataOutputStream(baos);
dao.writeShort(MAGIC);
dao.writeShort(VERSION);
writeBlock(dao, rsaCip.doFinal(bfKey), KEY_MARKER);
// writeBlock(dao,rsaCip.doFinal(aes_iv),IV_MARKER);
// aesCip.init(Cipher.ENCRYPT_MODE, aesKey,new
// IvParameterSpec(aes_iv));
writeBlock(dao, bfCip.doFinal(source), DATA_MARKER);
dao.flush();
return baos.toByteArray();
} catch (Exception ex) {
ExHandler.handle(ex);
}
return null;
}
private void writeBlock(DataOutputStream o, byte[] block, int marker) throws Exception{
o.writeShort(marker);
o.writeInt(block.length);
o.write(block);
for (int i = 0; i < block.length; i++) {
block[i] = 0;
}
}
public byte[] sign(byte[] source){
try {
// Signature sig = Signature.getInstance("SHA1withRSA", "BC");
Signature sig = Signature.getInstance(SIGNATURE_ALGO);
PrivateKey pk = km.getPrivateKey(userKey, pwd);
SecureRandom sr = new SecureRandom();
sig.initSign(pk, sr);
sig.update(source);
return sig.sign();
} catch (Exception ex) {
ExHandler.handle(ex);
}
return null;
}
public VERIFY_RESULT verify(byte[] data, byte[] signature, String signerKeyName){
try {
// Signature sig = Signature.getInstance("SHA1withRSA", "BC");
Signature sig = Signature.getInstance(SIGNATURE_ALGO);
PublicKey pk = km.getPublicKey(signerKeyName);
if (pk == null) {
return VERIFY_RESULT.SIGNER_UNKNOWN;
}
sig.initVerify(pk);
sig.update(data);
if (sig.verify(signature)) {
return VERIFY_RESULT.OK;
} else {
return VERIFY_RESULT.BAD_SIGNATURE;
}
} catch (Exception ex) {
ExHandler.handle(ex);
}
return VERIFY_RESULT.INTERNAL_ERROR;
}
public boolean hasCertificateOf(String alias){
return km.existsCertificate(alias);
}
public boolean hasKeyOf(String alias){
return km.existsPrivate(alias);
}
public boolean addCertificate(X509Certificate cert){
if (km.addCertificate(cert)) {
return km.save();
}
return false;
}
public boolean addCertificate(byte[] certEncoded){
ByteArrayInputStream bais = new ByteArrayInputStream(certEncoded);
X509Certificate cert;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) cf.generateCertificate(bais);
return addCertificate(cert);
} catch (CertificateException e) {
ExHandler.handle(e);
return false;
}
}
public KeyPair generateKeys(String alias, char[] keypwd, TimeTool validFrom, TimeTool validUntil){
KeyPair ret = km.generateKeys();
if (alias != null) {
X509Certificate cert =
generateCertificate(ret.getPublic(), alias, validFrom, validUntil);
try {
km.addKeyPair(ret.getPrivate(), cert, keypwd);
km.save();
} catch (Exception ex) {
ExHandler.handle(ex);
return null;
}
}
return ret;
}
public X509Certificate generateCertificate(PublicKey pk, String alias, TimeTool validFrom,
TimeTool validUntil){
PrivateKey priv = km.getPrivateKey(userKey, pwd);
try {
X509Certificate ret =
km.generateCertificate(pk, priv, userKey, alias, validFrom, validUntil);
return ret;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public String getUser(){
return userKey;
}
private byte[] generateBlowfishKey(){
try {
KeyGenerator key_gen = KeyGenerator.getInstance(SYMM_CIPHER_ALGO);
SecretKey key = key_gen.generateKey();
return key.getEncoded();
} catch (Exception ex) {
ExHandler.handle(ex);
return null;
}
}
private Key generateAESKey(){
try {
// KeyGenerator key_gen = KeyGenerator.getInstance("AES", "BC");
KeyGenerator key_gen = KeyGenerator.getInstance(KEY_ALGO);
key_gen.init(128, km.getRandom());
Key aes_key = key_gen.generateKey();
return aes_key;
} catch (Exception ex) {
ExHandler.handle(ex);
return null;
}
}
public X509Certificate getCertificate(String alias){
return km.getCertificate(alias);
}
public byte[] getCertificateEncoded(String alias) throws CryptologistException{
X509Certificate cert = getCertificate(alias);
if (cert != null) {
try {
return cert.getEncoded();
} catch (CertificateEncodingException ce) {
throw new CryptologistException("Could not encode certificate",
CryptologistException.ERR_CERTIFICATE_ENCODING);
}
}
return null;
}
public boolean isFunctional(){
return true;
}
public boolean removeCertificate(String alias){
return km.removeKey(alias);
}
}