/* * JavaCard software implementation of decoding for OAEP scheme. * Based on source code from BouncyCastle (www.bouncycastle.org) * Ported by Petr Svenda http://www.svenda.com/petr Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* USAGE: // allocate OAEP engine JC_OAEP oaep = new JC_OAEP(); // initialze cipher engine (e.g., RSACipher), hash engine (e.g., Sha1), optional encoding parameters (can be null, if not used), // NOTE: external_array_for_internal_work_can_be_null ... array used for internal computations (if null then array will be allocated interally) - should be RAM array for reasonable speed. you can reuse existing array. // if you will use let array to be allocated internally, set MAX_MASK_ARRAY_LENGTH to proper value (depending on cipher modulus - 200B is fine for RSA 2048 with SHA2-512) oaep.init(false, cipher_engine, hash_engine, optional_encoding_parameters_can_be_null, external_array_for_internal_work_can_be_null); // decode block of data from OAEP encoding decLen = oaep.decodeBlock(data_to_decode, start_offset_of_data, length_of_data); */ package oaep; import javacard.framework.*; import javacard.security.*; import javacardx.crypto.*; //import org.bouncycastle.crypto.engines.RSAEngine; //import org.bouncycastle.crypto.Digest; //import java.security.SecureRandom; /** * JavaCard software implemnetation of OAEP padding. Based on BouncyCastle implementation (www.bouncycastle.org). * Ported by Petr Svenda (petr@svenda.com) * Note: code still contains several parts of code commented out - this is intensional for debugging purposes in standard Java debugger (outside card, no JavaCard) * @author Petr Svenda */ // TODO: // 1. Add function for temporar storage of internal state of Sha-512 to bypass repeated processing of the same data // 2. Remove necessity for separate array for mask source data storage in decode() - is it possible? // 3. Allow to set external RAM array public class JC_OAEP { private final static boolean RSA_NOPAD_USED = true; private final static short MAX_MASK_ARRAY_LENGTH = (short) 200; // 200 is maximum value required if RSA2048bits OAEP is used public static final short OAEP_DECODE_FAIL = (short) 0x6003; //RSAEngine engine; Cipher engine; private byte[] defHash; private byte[] tempHash; private byte[] C; byte[] maskSource; //private Digest hash; //private Digest mgf1Hash; private MessageDigest hash; private MessageDigest mgf1Hash; private boolean forEncryption; public void init( // boolean forEncryption, Cipher engine, MessageDigest hash, byte[] encodingParams, byte[] externalMaskSourceArray) { this.engine = engine; this.hash = hash; this.mgf1Hash = hash; // this.forEncryption = forEncryption; tempHash = new byte[hash.getLength()]; //tempHash = JCSystem.makeTransientByteArray(hash.getLength(), JCSystem.CLEAR_ON_RESET); defHash = new byte[hash.getLength()]; //defHash = JCSystem.makeTransientByteArray(hash.getLength(), JCSystem.CLEAR_ON_RESET); C = new byte[4]; //C = JCSystem.makeTransientByteArray((byte) 4, JCSystem.CLEAR_ON_RESET); if (externalMaskSourceArray == null) maskSource = JCSystem.makeTransientByteArray(MAX_MASK_ARRAY_LENGTH, JCSystem.CLEAR_ON_RESET); else maskSource = externalMaskSourceArray; if (encodingParams != null) { hash.doFinal(encodingParams, (short) 0, (short) encodingParams.length, defHash, (short) 0); } else hash.doFinal(encodingParams, (short) 0, (short) 0, defHash, (short) 0); } /**/ /* // Use this function for pure-Java debugging (outside card, no javaCard) public void init( boolean forEncryption, RSAEngine engine, Digest hash, byte[] encodingParams) { this.engine = engine; this.hash = hash; this.mgf1Hash = hash; this.forEncryption = forEncryption; defHash = new byte[hash.getDigestSize()]; tempHash = new byte[hash.getDigestSize()]; maskSource = new byte[200]; // todo: 200 is maximum value we will be using with our implementation of RSA2048 OAEP C = new byte[4]; if (encodingParams != null) { hash.update(encodingParams, 0, encodingParams.length); } hash.doFinal(defHash, 0); } /**/ public short decodeBlock( byte[] in, short inOff, short inLen) throws ISOException { short decLen = engine.doFinal(in, inOff, inLen, in, inOff); // WE MUST REMOVE TRAILING ZEROES FROM DECRYPTED RESULT // IF RSA_PKCS1 IS USED, THAN IT WAS ALREADY PERFORMED // IF RSA_NOPAD IS USED, REMOVE FIRST BYTE if (RSA_NOPAD_USED) { if (in[inOff] != 0) { throw new ISOException(OAEP_DECODE_FAIL); } else { inOff++; decLen--; } } if (decLen < (short) ((2 * defHash.length) + 1)) { throw new ISOException(OAEP_DECODE_FAIL); } // // unmask the seed. // short maskSourceLength = (short) (decLen - (short) defHash.length); //byte[] maskSource = new byte[maskSourceLength]; Util.arrayCopyNonAtomic(in, (short) (inOff + defHash.length), maskSource, (short) 0, (short) maskSourceLength); maskGeneratorFunction1(maskSource, (short) 0, maskSourceLength, (short) defHash.length, in, inOff); // // unmask the message block. // maskSourceLength = (short) defHash.length; Util.arrayCopyNonAtomic(in, inOff, maskSource, (short) 0, (short) maskSourceLength); maskGeneratorFunction1(maskSource, (short) 0, maskSourceLength, (short) (decLen - defHash.length), in, (short) (inOff + defHash.length)); // // check the hash of the encoding params. // for (short i = 0; i != defHash.length; i++) { if (defHash[i] != in[(short) (inOff + defHash.length + i)]) { throw new ISOException(OAEP_DECODE_FAIL); } } // // find the data block // short start; for (start = (short) (inOff + 2 * defHash.length); start < (short) (inOff + decLen); start++) { if (in[start] == 1 || in[start] != 0) break; } if (start >= (short) (inOff + decLen - 1)) throw new ISOException(OAEP_DECODE_FAIL); if (in[start] != 1) throw new ISOException(OAEP_DECODE_FAIL); start++; // // extract the data block // short outputLength = (short) (decLen - start); start -= inOff; if (RSA_NOPAD_USED) { for (short i = inOff; i < (short) (inOff + outputLength); i++) in[(short) (i - 1)] = in[(short) (start + i)]; outputLength--; } else { for (short i = inOff; i < (short) (inOff + outputLength); i++) in[i] = in[(short) (start + i)]; } return outputLength; /**/ } private short maskGeneratorFunction1( byte[] Z, short zOff, short zLen, short length, byte[] bufferToMask, short bufferToMaskOffset) { short counter = 0; hash.reset(); mgf1Hash.reset(); // COPY INPUT // ASSUMPTION: WE WILL NOT PROCESS MORE THEN 128 BLOCKS C[0] = (byte)0; C[1] = (byte)0; C[2] = (byte)0; C[3] = (byte)-1; //for (counter = 0; counter < (short) (length / tempHash.length); counter++) { do { C[3] = (byte) (C[3] + 1); mgf1Hash.update(Z, zOff, zLen); mgf1Hash.doFinal(C, (short) 0, (short) C.length, tempHash, (short) 0); //mgf1Hash.update(C, 0, C.length); //mgf1Hash.doFinal(tempHash, 0); // MASK/UNMASK PART OF GIVEN ARRAY for (short i = 0; i < (short) tempHash.length; i++) bufferToMask[(short) (bufferToMaskOffset + i)] ^= tempHash[i]; bufferToMaskOffset += (short) (tempHash.length); } while (++counter < (short) (length / tempHash.length)); if ((short) (counter * tempHash.length) < length) { C[3] = (byte) (C[3] + 1); mgf1Hash.update(Z, zOff, zLen); mgf1Hash.doFinal(C, (short) 0, (short) C.length, tempHash, (short) 0); //mgf1Hash.update(C, 0, C.length); //mgf1Hash.doFinal(tempHash, 0); for (short i = 0; i < (short) tempHash.length; i++) bufferToMask[(short) (bufferToMaskOffset + i)] ^= tempHash[i]; bufferToMaskOffset += (short) tempHash.length; } return (short) 0; } }