jCryption for Java


Requirements

JDK 1.4+
Bouncy Castle Crypto APIs - http://www.bouncycastle.org/latest_releases.html - tested with bcprov-jdk14-136.jar

Java class

Just simple class in java to provide exact functionality as php version of library.


package com.fg.lib.shared.v1.cryptography;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.GeneralSecurityException;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
import java.util.HashMap;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;
import javax.crypto.Cipher;

/**
 * jCryption support (www.jcryption.org) - RSA encryption
 * @author Michal Franc, FG Forrest (c) 2009
 *         18.10.2009 10:35:24
 */
public class jCryption {

    /**
     * Constructor
     */
    public jCryption() {
        java.security.Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * Generates the Keypair with the given keyLength.
     *
     * @param keyLength length of key
     * @return KeyPair object
     * @throws RuntimeException if the RSA algorithm not supported
    */
    public KeyPair generateKeypair(int keyLength) {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(keyLength);
            return kpg.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("RSA algorithm not supported",e);
        }
    }

    /**
     * Decrypts a given string with the RSA keys
     * @param encrypted full encrypted text
     * @param keys RSA keys
     * @return decrypted text
     * @throws RuntimeException if the RSA algorithm not supported or decrypt operation failed
     */
    public String decrypt( String encrypted, KeyPair keys ) {
        Cipher dec;
        try {
            dec = Cipher.getInstance("RSA/NONE/NoPadding");
            dec.init(Cipher.DECRYPT_MODE, keys.getPrivate());
        } catch (GeneralSecurityException e) {
            throw new RuntimeException("RSA algorithm not supported",e);
        }
        String[] blocks = encrypted.split("\\s");
        StringBuffer result = new StringBuffer();
        try {
            for ( int i = blocks.length-1; i >= 0; i-- ) {
                byte[] data = hexStringToByteArray(blocks[i]);
                byte[] decryptedBlock = dec.doFinal(data);
                result.append( new String(decryptedBlock) );
            }
        } catch (GeneralSecurityException e) {
            throw new RuntimeException("Decrypt error",e);
        }
        return result.reverse().toString();
    }

    /**
     * Parse url string (Todo - better parsing algorithm)
     * @param url value to parse
     * @param encoding encoding value
     * @return Map with param name, value pairs
     */
    public static Map parse(String url,String encoding) {
        try {
            String urlToParse = URLDecoder.decode(url,encoding);
            String[] params = urlToParse.split("&");
            Map parsed = new HashMap();
            for (int i = 0; i<params.length; i++ )  {
                String[] p = params[i].split("=");
                String name = p[0];
                String value = (p.length==2)?p[1]:null;
                parsed.put(name, value);
            }
            return parsed;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unknown encoding.",e);
        }
    }

    /**
     * Return public RSA key modulus
     * @param keyPair RSA keys
     * @return modulus value as hex string
     */
    public static String getPublicKeyModulus( KeyPair keyPair ) {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        return publicKey.getModulus().toString(16);
    }

    /**
     * Return public RSA key exponent
     * @param keyPair RSA keys
     * @return public exponent value as hex string
     */
    public static String getPublicKeyExponent( KeyPair keyPair ) {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        return publicKey.getPublicExponent().toString(16);
    }

    /**
     * Max block size with given key length
     * @param keyLength length of key
     * @return numeber of digits
     */
    public static int getMaxDigits(int keyLength)   {
        return ((keyLength *2)/16)+3;
    }

    /**
     * Convert byte array to hex string
     * @param bytes input byte array
     * @return Hex string representation
     */
    public static String byteArrayToHexString(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (int i=0; i < bytes.length; i++) {
            result.append( Integer.toString( ( bytes[i] & 0xff ) + 0x100, 16).substring( 1 ) );
        }
        return result.toString();
    }

    /**
     * Convert hex string to byte array
     * @param data input string data
     * @return bytes
     */
    public static byte[] hexStringToByteArray(String data) {
        int k = 0;
        byte[] results = new byte[data.length() / 2];
        for (int i = 0; i < data.length();) {
            results[k] = (byte) (Character.digit(data.charAt(i++), 16) << 4);
            results[k] += (byte) (Character.digit(data.charAt(i++), 16));
            k++;
        }
        return results;
    }

}

Example

JSP example (not production code style, in single JSP to be simple)

<%@ page import="com.fg.lib.shared.v1.cryptography.jCryption" %>
<%@ page import="java.security.KeyPair" %>
<%@ page import="java.util.Map" %>
<%@ page contentType="text/html;charset=windows-1250" language="java" %><%
int KEY_SIZE = 1024;
String SESSION_KEY = "jCryptionKeys";
jCryption jc = new jCryption();
// generate keys
if (request.getParameter("generateKeypair")!=null) {
    KeyPair keys = jc.generateKeypair(KEY_SIZE);
    session.setAttribute(SESSION_KEY,keys);
    String e = jCryption.getPublicKeyExponent(keys);
    String n = jCryption.getPublicKeyModulus(keys);
    String md = String.valueOf(jCryption.getMaxDigits(KEY_SIZE));
    out.print("{\"e\":\"");
    out.print(e);
    out.print("\",\"n\":\"");
    out.print(n);
    out.print("\",\"maxdigits\":\"");
    out.print(md);
    out.print("\"}");
}
else    {
// process login
%><html>
    <head><title>Login form</title></head>
    <meta http-equiv="Content-Type" content="text/html; charset=windows-1250">
    <script type="text/javascript" src="<%=request.getContextPath()%>/rs/jquery-1.3.2.min.js"></script>
    <script type="text/javascript" src="<%=request.getContextPath()%>/rs/jquery.jcryption-1.0.1.min.js" ></script>
    <script type="text/javascript">
    $(document).ready(function() {
        var $statusText = $('<span id="status"></span>').hide();
        $("#status_container").append($statusText);
        $("#lf").jCryption({
            getKeysURL:"<%=request.getContextPath()%>/jc.jsp?generateKeypair=true",
            beforeEncryption:function() {$statusText.text("Šifrování - generování RSA klíče.").show();return true;},
            encryptionFinished:function(encryptedString, objectLength) {
                $statusText.text("Šifrování dokončeno, odesílám.");
                return true;
            }
        });
    });
    </script>
<body>
<%
    KeyPair keys = (KeyPair)request.getSession().getAttribute(SESSION_KEY);
    String encrypted = request.getParameter("jCryption");
    String username = null;
    String password = null;
    // decrypt
    if ( encrypted!=null && keys!=null )   {
        try {
            String data = jc.decrypt(encrypted, keys);
            request.getSession().removeAttribute(SESSION_KEY);
            Map params = jCryption.parse(data,"windows-1250");
            username = (String)params.get("user");
            password = (String)params.get("password");
        }   catch ( Throwable e )   {
            e.printStackTrace();
        }
    }
%>
posted user: <%=username%><br>
posted password: <%=password%><hr>
<form id="lf" action="<%=request.getContextPath()%>/jc.jsp" method="post">
    <fieldset>
        <legend>login</legend>
        <div>
            <div>username:<br><input type="text" size="45" name="user" value=""></div>
            <div>password:<br><input type="password" size="45" name="password" value=""></div>
        </div>
        <div>
            <p><input type="submit"/><span id="status_container"></span></p>
        </div>
    </fieldset>
</form>
</body>
</html><%
}
%>