/** * Copyright (c) 2009 Mark S. Kolich * http://mark.kolich.com * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.kolich.security; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Signature; import java.security.spec.PKCS8EncodedKeySpec; /** * Signs a byte array (a message) using a PKCS#8 encoded * RSA private key. * @author kolichko Mark S. Kolich * */ public class PKCS8RSASigner { private static final long MAX_KEY_SIZE_BYTES = 8192L; private static final String UTF_8 = "UTF-8"; private static final String SHA1_WITH_RSA = "SHA1withRSA"; private static final String SUN_JSSE = "SunJSSE"; private static final String RSA = "RSA"; private static final char HEX_DIGIT [] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private File pkcsKeyFile_; private byte [] keyFileBytes_; private Signature dsa_; private KeyFactory keyFactory_; private PrivateKey privateKey_; public PKCS8RSASigner ( File pkcsKeyFile ) { try { this.pkcsKeyFile_ = pkcsKeyFile; this.dsa_ = Signature.getInstance(SHA1_WITH_RSA, SUN_JSSE); this.keyFactory_ = KeyFactory.getInstance(RSA, SUN_JSSE); this.init(); } catch ( Exception e ) { // Wrap it, so every where that you use PKCS8RSASigner // you don't have to wrap the constructor in a try/catch. // But the caller should catch Error's though. throw new Error(e); } } /** * Given a message, generate a signature based on this * PKCS#8 private key. * @param message * @return * @throws Exception */ public byte [] getSignature ( byte [] message ) throws Exception { this.dsa_.update( message ); return this.dsa_.sign(); } /** * Setup this PKCS8RSASigner. Load the key file into * memory, and init the key factory accordingly. * @throws IOException */ private void init ( ) throws Exception { FileInputStream is = null; if ( !this.pkcsKeyFile_.exists() ) { throw new FileNotFoundException( "RSA key file not found!" ); } // Get the size, in bytes, of the key file. final long length = this.pkcsKeyFile_.length(); if ( length > MAX_KEY_SIZE_BYTES ) { throw new IOException( "Key file is too big!" ); } try { is = new FileInputStream( this.pkcsKeyFile_ ); int offset = 0; int read = 0; this.keyFileBytes_ = new byte[(int)length]; while ( offset < this.keyFileBytes_.length && (read=is.read(this.keyFileBytes_, offset, this.keyFileBytes_.length-offset)) >= 0 ) { offset += read; } } catch ( IOException ioe ) { throw ioe; } finally { try { if ( is != null ) { is.close(); } } catch ( IOException ioe ) { throw new Exception("Error, couldn't close FileInputStream", ioe); } } PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec( this.keyFileBytes_ ); // Get the private key from the key factory. this.privateKey_ = keyFactory_.generatePrivate( privKeySpec ); // Init the signature from the private key. this.dsa_.initSign( this.privateKey_ ); } /** * Convert a byte array into its hex String equivalent. * @param bytes * @return */ public static String toHex ( byte [] bytes ) { if ( bytes == null ) { return null; } StringBuilder buffer = new StringBuilder(bytes.length*2); for ( byte thisByte : bytes ) { buffer.append(byteToHex(thisByte)); } return buffer.toString(); } /** * Convert a single byte into its hex String * equivalent. * @param b * @return */ private static String byteToHex ( byte b ) { char [] array = { HEX_DIGIT[(b >> 4) & 0x0f], HEX_DIGIT[b & 0x0f] }; return new String(array); } /** * For testing and demonstration purposes. * @param args */ public static void main ( String [] args ) { // A bunch of sample messages to digitally sign // using your PKCS#8 encoded private key. String [] toSign = { "some string", "http://kolich.com", "bleh bleh bleh" }; // Create a new PKCS8RSASigner using the specified // PKCS#8 encoded RSA private key. PKCS8RSASigner signer = new PKCS8RSASigner( new File( "key.pkcs8" ) ); for ( String s : toSign ) { try { System.out.println( toHex( signer.getSignature( s.getBytes( UTF_8 ) ) ).toUpperCase() ); } catch ( Exception e ) { e.printStackTrace( System.err ); } } } }