All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.forwardsecrecy.ForwardSecrecyUtility Maven / Gradle / Ivy

// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// =============================================================================
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// together with the GPL linking exception applied; as being applied by the GNU
// Classpath ("http://www.gnu.org/software/classpath/license.html")
// =============================================================================
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// =============================================================================
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////

package org.refcodes.forwardsecrecy;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.jasypt.util.text.BasicTextEncryptor;
import org.refcodes.data.Delimiter;
import org.refcodes.data.License;
import org.refcodes.exception.Trap;
import org.refcodes.security.Algorithm;
import org.refcodes.textual.HorizAlignTextBuilder;
import org.refcodes.textual.HorizAlignTextMode;
import org.refcodes.textual.RandomTextGenerartor;
import org.refcodes.textual.RandomTextMode;

/**
 * The Class ForwardSecrecyUtility.
 */
public final class ForwardSecrecyUtility {

	// /////////////////////////////////////////////////////////////////////////
	// STATICS:
	// /////////////////////////////////////////////////////////////////////////

	public static final int CIPHER_LENGTH = 48;
	public static final int MESSAGE_LENGTH = 256;

	private static final Logger LOGGER = Logger.getLogger( ForwardSecrecyUtility.class.getName() );
	private static final BasicTextEncryptor TEXT_ENCRYPTOR;
	private static final RandomTextGenerartor RND_CIPHER_GENERATOR = new RandomTextGenerartor().withColumnWidth( CIPHER_LENGTH ).withRandomTextMode( RandomTextMode.ALPHANUMERIC );
	private static final RandomTextGenerartor RND_MESSAGE_GENERATOR = new RandomTextGenerartor().withColumnWidth( MESSAGE_LENGTH ).withRandomTextMode( RandomTextMode.ALPHANUMERIC );

	static {
		String thePassPhrase = null;
		final Enumeration e;
		try {
			e = NetworkInterface.getNetworkInterfaces();
			NetworkInterface eNetworkInterface;
			byte[] eHardwareAddress;
			while ( e.hasMoreElements() ) {
				eNetworkInterface = e.nextElement();
				try {
					eHardwareAddress = eNetworkInterface.getHardwareAddress();
					if ( eHardwareAddress != null ) {
						thePassPhrase = "";
						for ( int i = 0; i < eHardwareAddress.length; i++ ) {
							thePassPhrase += ( eHardwareAddress[i] < 0 ) ? Integer.toString( ( ( eHardwareAddress[i] ) & 0xFF ) ) : "" + eHardwareAddress[i];
							if ( i < eHardwareAddress.length - 1 ) {
								thePassPhrase += ".";
							}
						}
						break;
					}
				}
				catch ( SocketException se ) {
					LOGGER.log( Level.WARNING, "Unable to acquire network interfaces's (\"" + eNetworkInterface.getDisplayName() + "\") hardware address, trying next network interface as of:" + Trap.asMessage( se ), se );
				}
			}
		}
		catch ( SocketException se ) {
			LOGGER.log( Level.WARNING, "Unable to acquire machine's network interfaces (using alternate cipher) as of: " + Trap.asMessage( se ), se );
		}

		if ( thePassPhrase == null ) {
			thePassPhrase = License.REFCODES_LICENSE.getText();
		}

		TEXT_ENCRYPTOR = new BasicTextEncryptor();
		TEXT_ENCRYPTOR.setPassword( thePassPhrase );
	}

	// /////////////////////////////////////////////////////////////////////////
	// CONSTANTS:
	// /////////////////////////////////////////////////////////////////////////

	private static final String BOUNCY_CASTLE_PROVIDER = "BC";
	private static final char CIPHER_UID_TIMESTAMP_SEPARATOR = '-';

	public static final int CIPHER_UID_TIMESTAMP_LENGTH = 14;
	public static final int CIPHER_UID_LENGTH = 24;

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Private empty constructor to prevent instantiation as of being a utility
	 * with just static public methods.
	 */
	private ForwardSecrecyUtility() {}

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Returns true in case the given text provided the characteristics of an
	 * encrypted text as of the cipher and cipher UID pattern.
	 * -------------------------------------------------------------------------
	 * CAUTION: A plain text may have the same characteristics, an encrypted
	 * text must have these characteristics!
	 * -------------------------------------------------------------------------
	 * 
	 * @param aText The text to test whether it is encrypted.
	 * 
	 * @return True in case the given text has the characteristics of an
	 *         encrypted text
	 */
	public static boolean hasEncryptionPattern( String aText ) {
		if ( aText.length() < CIPHER_UID_LENGTH ) {
			return false;
		}
		if ( aText.charAt( CIPHER_UID_LENGTH ) != Delimiter.CIPHER_UID.getChar() ) {
			return false;
		}
		if ( aText.charAt( CIPHER_UID_TIMESTAMP_LENGTH - 1 ) != CIPHER_UID_TIMESTAMP_SEPARATOR ) {
			return false;
		}
		return true;
	}

	/**
	 * Expects a text with a prefixed cipher UID. Extracts the cipher UID with
	 * which the given text was encrypted.
	 * 
	 * @param aCipherUidWithEncryptedText The encrypted text with the prefixed
	 *        cipher UID
	 * 
	 * @return The cipher UID or null if none cipher UID was found
	 */
	public static String toCipherUidPrefix( String aCipherUidWithEncryptedText ) {
		final int theIndex = aCipherUidWithEncryptedText.indexOf( Delimiter.CIPHER_UID.getChar() );
		if ( theIndex == -1 ) {
			return null;
		}
		return aCipherUidWithEncryptedText.substring( 0, theIndex );
	}

	/**
	 * Expects a text with a prefixed cipher UID. Extracts the encrypted Text
	 * without the prefixed cipher UID.
	 * 
	 * @param aCipherUidWithEncryptedText The encrypted text with the prefixed
	 *        cipher UID
	 * 
	 * @return The encrypted text portion or null if none cipher UID was found
	 *         (then we have an invalid format of the provided text)
	 */
	public static String toEncryptedTextBody( String aCipherUidWithEncryptedText ) {
		final int theIndex = aCipherUidWithEncryptedText.indexOf( Delimiter.CIPHER_UID.getChar() );
		if ( theIndex == -1 ) {
			return null;
		}
		return aCipherUidWithEncryptedText.substring( theIndex + 1 );
	}

	/**
	 * Default way on how to create a cipher UID. In case the default way
	 * generated bad cipher UIDs, the default way's implementation is changed
	 * making it good again and all system using the default way. The timestamp
	 * is prepended so that regarding on the timestamp, encrypted texts can be
	 * easily selected, e.g. texts being encrypted with a cipher older than a
	 * given timestamp.
	 * 
	 * @return A cipher UID created the default way.
	 */
	public static String createCipherUid() {
		String theCipherUid;
		theCipherUid = Long.toString( System.currentTimeMillis() ) + CIPHER_UID_TIMESTAMP_SEPARATOR;
		theCipherUid = new HorizAlignTextBuilder().withHorizAlignTextMode( HorizAlignTextMode.RIGHT ).withText( theCipherUid ).withColumnWidth( CIPHER_UID_TIMESTAMP_LENGTH ).withFillChar( '0' ).toString();
		theCipherUid = theCipherUid + new RandomTextGenerartor().withColumnWidth( CIPHER_UID_LENGTH - theCipherUid.length() ).withRandomTextMode( RandomTextMode.ALPHANUMERIC ).next();
		return theCipherUid;
	}

	/**
	 * Default way on how to create a cipher. In case the default way generated
	 * bad ciphers, the default way's implementation is changed making it secure
	 * again and all system using the default way.
	 * 
	 * @return A cipher created the default way.
	 */
	public static String createCipher() {
		return RND_CIPHER_GENERATOR.next();
	}

	/**
	 * Default way on how to create a aMessage which is to be signed in order to
	 * identify the owner of a public key.
	 * 
	 * @return A aMessage created the default way.
	 */
	public static String createMessage() {
		return RND_MESSAGE_GENERATOR.next();
	}

	public static PrivateKey readPrivateKey( File aFile, String aPassword ) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
		final InputStream res = new FileInputStream( aFile );
		try ( Reader theReader = new BufferedReader( new InputStreamReader( res ) ); PEMParser theParser = new PEMParser( theReader ) ) {
			PEMKeyPair theKeyPair = null;
			// Password |-->
			if ( aPassword != null ) {
				final PEMDecryptorProvider theDecryptorProvider = new JcePEMDecryptorProviderBuilder().setProvider( BOUNCY_CASTLE_PROVIDER ).build( aPassword.toCharArray() );
				IOException theIoException = null;
				Object eObj;
				while ( ( eObj = theParser.readObject() ) != null ) {
					if ( eObj instanceof PEMEncryptedKeyPair ) {
						try {
							theKeyPair = ( (PEMEncryptedKeyPair) eObj ).decryptKeyPair( theDecryptorProvider );
							break;
						}
						catch ( IOException e ) {
							if ( theIoException == null ) {
								theIoException = e;
							}
						}
					}
				}
				if ( theKeyPair == null ) {
					if ( theIoException != null ) {
						throw theIoException;
					}
					throw new IOException( "No key-pair found in file <" + aFile.getAbsolutePath() + ">." );
				}
			}
			// Password <--|
			else {
				Object eObj;
				while ( ( eObj = theParser.readObject() ) != null ) {
					if ( eObj instanceof PEMKeyPair ) {
						theKeyPair = (PEMKeyPair) eObj;
						break;
					}
				}
				if ( theKeyPair == null ) {
					throw new IOException( "No key-pair found in file <" + aFile.getAbsolutePath() + ">." );
				}
			}

			final PKCS8EncodedKeySpec theKeySpec = new PKCS8EncodedKeySpec( theKeyPair.getPrivateKeyInfo().getEncoded() );
			final KeyFactory theKeyFactory = KeyFactory.getInstance( Algorithm.RSA.getName() );
			final PrivateKey thePrivateKey = theKeyFactory.generatePrivate( theKeySpec );
			return thePrivateKey;
		}
	}

	public static PublicKey readPublicKey( File aFile ) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
		try ( Reader theReader = new BufferedReader( new InputStreamReader( new FileInputStream( aFile ) ) ); PEMParser theParser = new PEMParser( theReader ) ) {
			PEMKeyPair theKeyPair = null;
			Object eObj;
			final SubjectPublicKeyInfo eInfo;
			while ( ( eObj = theParser.readObject() ) != null ) {
				if ( eObj instanceof PEMKeyPair ) {
					theKeyPair = (PEMKeyPair) eObj;
					final PKCS8EncodedKeySpec theKeySpec = new PKCS8EncodedKeySpec( theKeyPair.getPrivateKeyInfo().getEncoded() );
					final KeyFactory theKeyFactory = KeyFactory.getInstance( Algorithm.RSA.getName() );
					final PublicKey thePublicKey = theKeyFactory.generatePublic( theKeySpec );
					return thePublicKey;
				}
				if ( eObj instanceof SubjectPublicKeyInfo ) {
					eInfo = (SubjectPublicKeyInfo) eObj;
					final RSAKeyParameters theRsaParam = (RSAKeyParameters) PublicKeyFactory.createKey( eInfo.getEncoded() );
					final RSAPublicKeySpec theRsaSpec = new RSAPublicKeySpec( theRsaParam.getModulus(), theRsaParam.getExponent() );
					final KeyFactory theKeyFactory = KeyFactory.getInstance( Algorithm.RSA.getName() );
					final PublicKey thePublicKey = theKeyFactory.generatePublic( theRsaSpec );
					return thePublicKey;
				}
			}
		}
		throw new IOException( "No key-pair found in file <" + aFile.getAbsolutePath() + ">." );
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy