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() + ">." );
}
}