I wrote the below simple program to generate a RSA key pair and transfer the public key to outside the card in the APDU response:
public class CryptoRSA extends Applet {
//Abbreviations
private static final boolean NO_EXTERNAL_ACCESS = false;
//Switch case parameters for selecting instruction = INS in apdu command
private static final byte GENERATE_KEY_PAIR = (byte) 0xC0;
//Create object of keys
RSAPrivateKey thePrivateKey = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_512, NO_EXTERNAL_ACCESS);
RSAPublicKey thePublickKey = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_512, NO_EXTERNAL_ACCESS);
KeyPair theKeyPair = new KeyPair(thePublickKey, thePrivateKey);
public static void install(byte[] bArray, short bOffset, byte bLength) {
new CryptoRSA();
}
protected CryptoRSA() {
register();
}
public void process(APDU apdu) {
if (selectingApplet()) {
return;
}
byte[] buffer = apdu.getBuffer();
short privateKeySize = 0;
short publicKeySize = 0;
byte[] publicArray;
byte[] privateArray;
try {
switch (buffer[ISO7816.OFFSET_INS]) {
case GENERATE_KEY_PAIR:
theKeyPair.genKeyPair();
PrivateKey thePrivateKey = theKeyPair.getPrivate();
PublicKey thePublicKey = theKeyPair.getPublic();
publicKeySize = thePrivateKey.getSize();
privateKeySize = thePrivateKey.getSize();
byte[] publicKey = JCSystem.makeTransientByteArray((short) (publicKeySize), JCSystem.CLEAR_ON_DESELECT);
((RSAPublicKey) thePrivateKey).getExponent(publicKey, (short) publicKeySize);
Util.arrayCopyNonAtomic(publicKey, (short) 0, buffer, (short) 0, (short) (publicKeySize ));
apdu.setOutgoingAndSend((short) 0, (short) (publicKeySize));
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
} catch (Exception e) {
if (e instanceof CryptoException) {
short r = ((CryptoException) e).getReason();
ISOException.throwIt(r);
} else {
ISOException.throwIt((short) 0x8888);
}
}
}
}
But when I send the related APDU command to the card, I receive 0x8888 as below:
OSC:: opensc-tool.exe -s 00a40400060102030405dd -s 00c00000
Using reader with a card: ACS CCID USB Reader 0
Sending: 00 A4 04 00 06 01 02 03 04 05 DD
Received (SW1=0x90, SW2=0x00)
Sending: 00 C0 00 00
Received (SW1=0x88, SW2=0x88)
getSize() returns the bit length of the key, not the byte length. You are probably running out of RAM.
2.((RSAPublicKey) thePrivateKey).getExponent(publicKey, (short) publicKeySize);
This won't work! You are asking for the exponent to be stored at offset publicKeySize in array publicKey -- that is, at the very end of the array, where there are precisely 0 bytes left to store it.
By the way, the next time you come across a problem like this you can use ISOException to send debugging data to the outside world. For instance, ISOException.throwIt(privateKeySize) would have found problem 1.
Related
My HC 05 Bluetooth module is connected with 8051 at receive side.
I want to send simple text file using android mobile to HC 05 module.
How could i do that?
If you use Java.
First of all, you should get default Bluetooth adapter:
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
Next you should get MAC address of your HC-05, you can just check it in connection list in Bluetooth settings of your phone, or you can scan nearby devices in your app and check their names:
/* Check HC-05 in paired devices */
public void findInPairedDevices() {
Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
if (device.getName().equals("HC-05")) {
MACaddress = device.getAddress();
}
}
}
}
/* Scan nearby devices for HC-05 */
public void findInAvailableDevices() {
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getName().equals("HC-05")) {
MACaddress = device.getAddress();
adapter.cancelDiscovery();
}
}
}
};
activity.registerReceiver(broadcastReceiver, filter);
int MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
/* Permission for Bluetooth search */
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
adapter.startDiscovery();
}
After getting MAC address you can get your HC-05:
BluetoothDevice device = adapter.getRemoteDevice(MACaddress);
And can create socket:
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(myUUID);
Where myUUID is your UUID which you can get using:
myUUID = UUID.fromString("Your UUID string");
Finally, start socket:
socket.connect();
Next you can use InputStream and OutputStream for communicating with HC-05:
if (socket.isConnected()) {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
}
For reading input message:
byte[] buffer = new byte[256];
int bytes;
bytes = inputStream.read(buffer);
String inMsg = new String(buffer, 0, bytes);
And for writing output message:
String outStr = "Meow";
outputStream.write(outStr.getBytes());
After work you should close streams and socket:
inputStream.close();
outputStream.close();
socket.close();
Note: you mustn't forget about exceptions.
I'm developing a piece of code in which I need to create an RSA key pair.
My java card is an FM1280-ID006 card which has written in its information sheet that It supports java card 2.2.2 specification. When I try to create an RSA key pair
KeyPair(KeyPair.ALG_RSA,KeyBuilder.LENGTH_RSA_2048)
and install it on the card above, nothing unusual happens and I'm done successfully (90 00).
but as I call the method genKeyPair(), something happens in the card which I do not understand why. I use GPShell as below:
send_apdu -sc 1 -APDU 8000000000 // initialize
Command --> 8000000000
Wrapped command --> 8000000000
send_APDU() returns 0x8010002F (A communications error with the smart card has been detected. Retry the operation.)
It is worthy to mention that this problem on key pair creation does not happen when creating a 1024 key pair
KeyPair(KeyPair.ALG_RSA,KeyBuilder.LENGTH_RSA_1024)
and it is successfully created and used by RSA cipher and so on as:
send_apdu -sc 1 -APDU 8000000000 // initialize
Command --> 8000000000
Wrapped command --> 8000000000
Response <-- 9000
send_APDU() returns 0x80209000 (9000: Success. No error.)
command time: 3432 ms
Does my card support KeyPair 2048 as the information sheet says supporting javacard 2.2.2?
Appendix 1: Java Code:
package net.sourceforge.globalplatform.jc.hasincard;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;
import java.security.acl.Owner;
import java.util.Random;
public class HasinCardApplet extends Applet {
final static byte APPLET_CLA = (byte)0x80;
final static byte INITIALIZE = (byte)0x00;
final static byte INSERT_MESSAGE = (byte)0x01;
final static byte CHECK_MESSAGE = (byte)0x02;
final static byte GET_HASH = (byte)0x03;
final static byte GET_SIGN = (byte)0x04;
final static byte VERIFY_SIGN = (byte)0x05;
final static byte VERIFY_PIN = (byte)0x06;
final static byte CHANGE_PIN = (byte)0x07;
final static byte UNLOCK_PIN = (byte)0x08;
final static byte TEST = (byte)0x09;
final static short SW_MESSAGE_NOT_INSERTED = (short)0x6300;
final static short SW_KEY_IS_INITIALIZED = (short)0x6301;
final static short SW_HASH_NOT_SET = (short)0x6302;
final static short SW_SIGN_NOT_SET = (short)0x6303;
final static short SW_CARD_IS_LOCKED = (short)0x6304;
final static short SW_VERIFICATION_FAILED = (short)0x6305;
final static short SW_PIN_VERIFICATION_REQUIRED = (short)0x6306;
final static short SW_NEW_PIN_TOO_LONG = (short)0x6307;
final static short SW_NEW_PIN_TOO_SHORT = (short)0x6308;
final static short SW_PUK_VERIFICATION_REJECTED = (short)0x6309;
final static short SW_PUK_VERIFICATION_FAILED = (short)0x630A;
final static byte PIN_TRY_LIMIT = (byte)3;
final static byte MAX_PIN_SIZE = (byte)16;
final static byte MIN_PIN_SIZE = (byte)4;
final static byte PUK_LEN = (byte)16;
final static byte PUK_TRY_LIMIT = (byte)3;
private static byte[] Message;
private static byte[] Hash;
private static byte[] Sign;
private short message_len = 0;
private short hash_len = 0;
private short sign_len = 0;
private static boolean key_initialization_flag = false;
private static boolean insert_message_flag = false;
private static boolean hash_set_flag = false;
private static boolean sign_set_flag = false;
MessageDigest mDigest;
KeyPair rsaKey;
Cipher rsaCipher;
OwnerPIN userPin;
RandomData rnd;
byte[] PUK;
boolean init_flag;
byte puk_try_count;
private HasinCardApplet (byte[] bArray,short bOffset,byte bLength)
{
Message = new byte[512];
Hash = new byte[512];
Sign = new byte[512];
userPin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
rsaKey = new KeyPair(KeyPair.ALG_RSA,KeyBuilder.LENGTH_RSA_2048);
rsaCipher = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
mDigest = MessageDigest.getInstance(MessageDigest.ALG_SHA_256,false);
rnd = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
PUK = new byte[PUK_LEN];
generate_random(PUK, (byte) 16);
init_flag = false;
puk_try_count = (byte) 0;
byte[] InstParam = new byte[(byte)64];
byte iLen = bArray[bOffset];
bOffset = (short) (bOffset+iLen+1);
byte cLen = bArray[bOffset];
bOffset = (short) (bOffset+cLen+1);
byte aLen = bArray[bOffset];
Util.arrayCopy(bArray,(short) (bOffset +1),InstParam,(short)0,(short)aLen);
byte CPassLen = InstParam[0];
byte IPassLen = InstParam[CPassLen+1];
userPin.update(InstParam,(short)1,CPassLen);
register();
}
public static void install(byte[] bArray, short bOffset, byte bLength)
{
new HasinCardApplet(bArray, bOffset, bLength);
}
public boolean select()
{
Util.arrayFillNonAtomic(Message, (short) 0, (short)512, (byte) 0x00);
Util.arrayFillNonAtomic(Hash, (short) 0, (short)512, (byte) 0x00);
Util.arrayFillNonAtomic(Sign, (short) 0, (short)512, (byte) 0x00);
return true;
}
public void deselect()
{
}
public void process(APDU apdu)
{
if (selectingApplet())
{
if(!init_flag) {
send_puk(apdu);
}
return;
}
byte[] buffer = apdu.getBuffer();
if (buffer[ISO7816.OFFSET_CLA] == APPLET_CLA) {
switch (buffer[ISO7816.OFFSET_INS]) {
case VERIFY_PIN:
verify_pin(apdu);
break;
case CHANGE_PIN:
change_pin(apdu);
break;
case UNLOCK_PIN:
unlock_pin(apdu);
break;
case INITIALIZE:
initialize();
break;
case INSERT_MESSAGE:
insert_message(apdu);
break;
case CHECK_MESSAGE:
check_message(apdu);
break;
case GET_HASH:
hash_message();
get_hash(apdu);
break;
case GET_SIGN:
hash_message();
sign_message();
get_sign(apdu);
break;
case VERIFY_SIGN:
verify_sign(apdu);
break;
case TEST:
test(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
} else {
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
}
}
private void initialize() {
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(key_initialization_flag) {
ISOException.throwIt(SW_KEY_IS_INITIALIZED);
}
rsaKey.genKeyPair();
key_initialization_flag = true;
init_flag = true;
}
private void insert_message(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(buffer[ISO7816.OFFSET_P1] == (byte) 0x01) {
// reset Message
message_len = 0;
}
short LC = apdu.setIncomingAndReceive();
Util.arrayCopy(buffer, (short) (ISO7816.OFFSET_CDATA), Message, message_len, LC);
message_len = (short) (message_len + LC);
insert_message_flag = true;
}
private void check_message(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(insert_message_flag) {
apdu.setIncomingAndReceive();
Util.arrayCopy(Message, (short) 0, buffer, (short) 0, message_len);
apdu.setOutgoingAndSend((short) 0, message_len);
} else {
ISOException.throwIt(SW_MESSAGE_NOT_INSERTED);
}
}
private void hash_message() {
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(insert_message_flag) {
mDigest.reset();
hash_len = mDigest.doFinal(Message, (short) 0, message_len, Hash, (short) 0);
hash_set_flag = true;
} else {
ISOException.throwIt(SW_MESSAGE_NOT_INSERTED);
}
}
private void sign_message() {
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(insert_message_flag) {
rsaCipher.init(rsaKey.getPrivate(), Cipher.MODE_ENCRYPT);
sign_len = rsaCipher.doFinal(Hash, (short) 0, hash_len, Sign, (short) 0);
sign_set_flag = true;
} else {
ISOException.throwIt(SW_MESSAGE_NOT_INSERTED);
}
}
private void get_hash(APDU apdu) {
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(hash_set_flag) {
byte[] buffer = apdu.getBuffer();
Util.arrayCopy(Hash, (short) 0, buffer, (short) 0, hash_len);
apdu.setOutgoingAndSend((short) 0, hash_len);
} else {
ISOException.throwIt(SW_HASH_NOT_SET);
}
}
private void get_sign(APDU apdu) {
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
if(sign_set_flag) {
byte[] buffer = apdu.getBuffer();
Util.arrayCopy(Sign, (short) 0, buffer, (short) 0, sign_len);
apdu.setOutgoingAndSend((short) 0, sign_len);
} else {
ISOException.throwIt(SW_SIGN_NOT_SET);
}
}
private void verify_sign(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
apdu.setIncomingAndReceive();
rsaCipher.init(rsaKey.getPublic(), Cipher.MODE_DECRYPT);
short LC = rsaCipher.doFinal(Sign, (short) 0, sign_len, buffer, (short)0);
apdu.setOutgoingAndSend((short)0,LC);
}
private void verify_pin(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if(userPin.getTriesRemaining() == (byte)0)
ISOException.throwIt(SW_CARD_IS_LOCKED);
short LC = (byte)(apdu.setIncomingAndReceive());
if ( userPin.check(buffer, ISO7816.OFFSET_CDATA, (byte)LC) == false )
ISOException.throwIt(SW_VERIFICATION_FAILED);
}
private void change_pin(APDU apdu) {
byte[] buffer = apdu.getBuffer();
if(userPin.getTriesRemaining() == (byte)0)
ISOException.throwIt(SW_CARD_IS_LOCKED);
if ( ! userPin.isValidated() )
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
short LC = (byte)(apdu.setIncomingAndReceive());
if(LC > MAX_PIN_SIZE)
ISOException.throwIt(SW_NEW_PIN_TOO_LONG);
if(LC < MIN_PIN_SIZE)
ISOException.throwIt(SW_NEW_PIN_TOO_SHORT);
userPin.update(buffer, (short) ISO7816.OFFSET_CDATA, (byte)LC);
}
private void unlock_pin(APDU apdu){
if(puk_try_count >= PUK_TRY_LIMIT){
ISOException.throwIt(SW_PUK_VERIFICATION_REJECTED);
}
byte[] buffer = apdu.getBuffer();
apdu.setIncomingAndReceive();
if (Util.arrayCompare(PUK, (short) 0, buffer, (short) (ISO7816.OFFSET_CDATA), (short) PUK_LEN) == 0) {
userPin.resetAndUnblock();
puk_try_count = (byte) 0;
return;
} else {
puk_try_count = (byte)(puk_try_count + (byte)1);
ISOException.throwIt(SW_PUK_VERIFICATION_FAILED);
}
}
private void generate_random(byte[] buffer,byte len){
rnd.generateData(buffer, (short) 0, (short) len);
}
private void send_puk(APDU apdu){
apdu.setIncomingAndReceive();
byte[] buffer = apdu.getBuffer();
Util.arrayCopy(PUK,(short)0,buffer,(short)0,(short)PUK_LEN);
apdu.setOutgoingAndSend((short) 0, (short) PUK_LEN);
}
}
Appendix 2: GPShell 1.4.4 APDUs:
mode_211
enable_trace
establish_context
enable_trace
enable_timer
card_connect
command time: 15 ms
select -AID E0E1E2E3E4E501
Command --> 00A4040007E0E1E2E3E4E501
Wrapped command --> 00A4040007E0E1E2E3E4E501
Response <-- 26759506800664A82A242E481CD645C59000
command time: 78 ms
send_apdu -sc 1 -APDU 80060000083132333400000000 // verify userPin
Command --> 80060000083132333400000000
Wrapped command --> 80060000083132333400000000
Response <-- 9000
send_APDU() returns 0x80209000 (9000: Success. No error.)
command time: 16 ms
send_apdu -sc 1 -APDU 8000000000 // initialize
Command --> 8000000000
Wrapped command --> 8000000000
send_APDU() returns 0x8010002F (A communications error with the smart card has been detected. Retry the operation.)
I would like to sign some data (the MESSAGE byte array) on my Java Card and then return the signature in a response APDU. My code works fine (or at least I think it does and it returns 9000) without the line apdu.sendBytes(BAS, sSignLen), but when I uncomment it I get an unknown error (0xC000002B (Unknown error.)).
When I try to send other data in a response APDU it works flawlessly.
apdu.setIncomingAndReceive();
Util.arrayCopyNonAtomic(MESSAGE, (short) 0, buffer, (short) 0, (short) MESSAGE.length);
apdu.setOutgoingAndSend((short) 0, (short) MESSAGE.length);
Here is my code. What am I doing wrong or missing? Thank you!
public class TestApplet extends Applet {
...
private final static byte SIGN = (byte) 0x01;
...
private final static byte[] MESSAGE = new byte[] { 'M', 'e', 's', 's', 'a', 'g', 'e' };
final static short BAS = 0;
public void process(APDU apdu) {
if (this.selectingApplet())
return;
byte buffer[] = apdu.getBuffer();
...
switch (buffer[ISO7816.OFFSET_INS]) {
case SIGN:
try {
ECDSAKeyPair = Secp256k1Domain.getKeyPairParameter();
ECDSAKeyPair.genKeyPair();
ECDSAPublicKey = (ECPublicKey) ECDSAKeyPair.getPublic();
ECDSAPrivateKey = (ECPrivateKey) ECDSAKeyPair.getPrivate();
ECDSASignature = Signature.getInstance(Signature.ALG_ECDSA_SHA, false);
short signLen = 0;
byte[] signatureArray = new byte[70];
ECDSASignature.init(ECDSAPrivateKey, Signature.MODE_SIGN);
signLen = ECDSASignature.sign(MESSAGE, BAS, (short) MESSAGE.length, signatureArray, BAS);
apdu.setIncomingAndReceive();
Util.arrayCopyNonAtomic(signatureArray, (short) 0, buffer, (short) 0, (short) signatureArray.length);
apdu.setOutgoingAndSend((short) 0, (short) signatureArray.length);
} catch (CryptoException c) {
short reason = c.getReason();
ISOException.throwIt((short) ((short) (0x9C00) | reason));
}
break;
...
return;
}
}
It's probably that signLen is larger than the Ne value (incorrectly called Le in the JavaCard specifications). You are also abusing the Le value to mean (short) MESSAGE.length by the way. Ne indicates the maximum number of bytes that are expected to be send back.
I am working on migration of code form java to Nodejs. I have one requirement to encrypt the text with private key using "DESede/ECB/NoPadding" algorithm. Currently code is written in Java and now I need to migrate to Nodejs. Since encrypted key is sent to other application therefore I can't change the algorithm or key here. Following is approach used in java
1. Stored the private key in hex string. I.e. 48 chars hex string as below which is equivalent to 24 bytes reuquired for 3des
73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2
2. Following is code written in java
https://github.com/dilipkumar2k6/3des/blob/master/TripleDes.java
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class TripleDes {
// Crypto library related keys
private static final String ALGO_NAME = "DESede/ECB/NoPadding";
private static final int PADDING_BLOCK = 8;
// Test Data
private static final String PLAIN_TEXT = "Hello World";
private static final String SHARED_KEY = "73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2";
public static void main(String[] arg) {
try {
// Get Algorithm name
String desAlgoName = getDESAlgorithmName(ALGO_NAME);
// Create Cipher object
Cipher cipher = Cipher.getInstance(ALGO_NAME);
//Actual DES algo needs 56 bits key, which is equivalent to 1byte (0 at 0th position) Get 8*3 byets key
byte [] key = hexFromString(SHARED_KEY);
System.out.println("DES Algorithm shared key size in bytes >> "+key.length);
// Create SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(key, desAlgoName);
//Encrypt bytes
byte [] encryptedBytes = encryptIntoBytes(cipher, secretKeySpec, PLAIN_TEXT.getBytes(), 0, PLAIN_TEXT.getBytes().length);
String encryptedString= hexToString(encryptedBytes);
System.out.println(encryptedString);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static byte[] encryptIntoBytes(Cipher cipher, SecretKeySpec secretKeySpec, byte[] dct, int offset, int len) throws GeneralSecurityException {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ect = cipher.doFinal(addPadding(dct, offset, len));
return ect;
}
public static String getDESAlgorithmName(String algoName) {
System.out.println("getDESAlgorithmName algoName >> "+algoName);
String desAlgoName = null;
int i = algoName.indexOf("/");
if (i != -1)
desAlgoName = algoName.substring(0, i);
else
desAlgoName = algoName;
return desAlgoName;
}
/**
* Adds padding characters to the data to be encrypted. Also adds random
* Initial Value to the beginning of the encrypted data when using Triple
* DES in CBC mode (DES-EDE3/CBC).
*
* #param inData
* Array of bytes to be padded
* #param offset
* Offset to starting point within array
* #param len
* Number of bytes to be encrypted
* #return Padded array of bytes
*/
public static byte[] addPadding(byte[] inData, int offset, int len) {
System.out.println("addPadding offset >> "+offset+", len >> "+len);
byte[] bp = null;
int padChars = PADDING_BLOCK; // start with max padding value
int partial = (len + 1) % padChars; // calculate how many extra bytes
// exist
if (partial == 0) {
padChars = 1; // if none, set to only pad with length byte
} else {
padChars = padChars - partial + 1; // calculate padding size to
// include length
}
System.out.println("addPadding >> Add padding of "+padChars);
/*
* Create a byte array large enough to hold data plus padding bytes The
* count of padding bytes is placed in the first byte of the data to be
* encrypted. That byte is included in the count.
*/
bp = new byte[len + padChars];
bp[0] = Byte.parseByte(Integer.toString(padChars));
System.arraycopy(inData, offset, bp, 1, len);
return bp;
}
public static byte[] hexFromString(String hex) {
int len = hex.length();
byte[] buf = new byte[((len + 1) / 2)];
int i = 0, j = 0;
if ((len % 2) == 1)
buf[j++] = (byte) fromDigit(hex.charAt(i++));
while (i < len) {
buf[j++] = (byte) ((fromDigit(hex.charAt(i++)) << 4) | fromDigit(hex
.charAt(i++)));
}
return buf;
}
public static int fromDigit(char ch) {
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
throw new IllegalArgumentException("invalid hex digit '" + ch + "'");
}
public static String hexToString(byte[] ba) {
return hexToString(ba, 0, ba.length);
}
public static final char[] hexDigits = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static String hexToString(byte[] ba, int offset, int length) {
char[] buf = new char[length * 2];
int j = 0;
int k;
for (int i = offset; i < offset + length; i++) {
k = ba[i];
buf[j++] = hexDigits[(k >>> 4) & 0x0F];
buf[j++] = hexDigits[k & 0x0F];
}
return new String(buf);
}
}
I need to migrate this code to Nodejs and facing multiple issues. I refeered http://mygo.iteye.com/blog/2018882 to get the basic idea on nodejs way to do the encryption in des3. However I see following difference in JAVA way and Nodejs way.
1. JAVA is using Hex string of 48 lenght as key, since one char in hex is 4 bits therfore final size is equivalent to 24 bytes length which meets DES3 requirement.
2. In Java code, final key is being used as bytes (as needed by DES) which made indpendent of the way we store the key
3. In node js, key is stored as character i.e. to use des3 I have to use 24 bytes which is equivalent to 24 chars key as 73AD9CEC99816AA6A4D82FB2. Here this is string of 24 chars and since one char is one byte thereofore total length is 24 bytes which meets DES3 requirement.
4. Following is nodejs code for reference
https://github.com/dilipkumar2k6/3des/blob/master/Crypto.js
'use strict';
/*
* Offers related services.
*/
var crypto = require("crypto");
module.exports = {
encrypt: function (plainText) {
return encrypt({
alg: 'des-ede3', //3des-ecb
autoPad: true,
key: '73AD9CEC99816AA6A4D82FB2',
plaintext: 'Hello World',
iv: null
});
}
};
function encrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0);
var plaintext = param.plaintext;
var alg = param.alg;
var autoPad = param.autoPad;
//encrypt
var cipher = crypto.createCipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad); //default true
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
console.log(alg, ciph);
return ciph;
}
function decrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0)
var alg = param.alg;
var autoPad = param.autoPad;
//decrypt
var decipher = crypto.createDecipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad);
var txt = decipher.update(ciph, 'hex', 'utf8');
txt += decipher.final('utf8');
console.log(alg, txt);
return txt;
}
Following is my problem.
1. How can i convert my existing hex code into string? I used "hexToString" method (please check the java code)to convert hex into string. However getting weired character (this is also expected but problem is how i can use this transformed key in nodejs.
2. Can I pass byte array as key to Nodejs? It will make problem easy as I can easily convert my hex key into bytes array and I store my bytes array key in nodejs code.
3. In my javacode, I have custom padding logic, how can i write same logic in nodejs?
4. Most importantly, can I achieve same encryption logic in nodejs (similar to java)?
Artjom B. helped me to get the insight of nodejs and des3 algorithm. I have edited my post to clarify my exact requirement.
I think my main problem is, how can i feed byte[] as key to nodejs crypto for DES3?
I am kind of stuck. Please help.
Running crypto.getCiphers() shows you the available ciphers. Triple DES (EDE) in ECB mode with two keys (16 byte key) can be used as des-ede. If you have three part key (24 byte key) you should use des-ede3. ecb probably does not appear in the cipher description, because it's the most basic form.
Triple DES-EDE has different ways to use a key. EDE means encrypt-decrypt-encrypt with three different keys. If you only have for example one 8 byte key, this suggests that you use the same key for every phase of EDE. It's clear from your Java code that you have a 24 byte key (48 hex encoded chars). You have to use the same key.
The crypto module uses PKCS7 padding by default, so you will need to set the auto padding to false and do the padding yourself. I leave that task up to you.
module.exports = {
encrypt: function (plainText) {
return encrypt({
alg: 'des-ede3', //3des-ecb
autoPad: false,
key: '73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2',
plaintext: 'Hello World',
iv: null
});
}
};
function mypad(buf){
// TODO: do the padding
// replicate padding as in Java
return buf;
}
function myunpad(buf){
// TODO: do the unpadding
// read the first *byte* and remove as many trailing *bytes*
return buf;
}
function encrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0);
var plaintext = mypad(new Buffer(param.plaintext));
var alg = param.alg;
var autoPad = param.autoPad;
//encrypt
var cipher = crypto.createCipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad); //default true
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
console.log(alg, ciph);
return ciph;
}
function decrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0)
var alg = param.alg;
var autoPad = param.autoPad;
//decrypt
var decipher = crypto.createDecipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad);
var txt = decipher.update(ciph, 'hex', 'utf8');
txt += decipher.final('utf8');
console.log(alg, txt);
return myunpad(new Buffer(txt, 'hex'));
}
Word of caution:
Don't use (3)DES especially with only one 8 byte key! Don't use ECB mode! Don't use NoPadding for block modes! Use AES-256 with GCM mode (no padding since it's a streaming mode).-
This is my decrypt function based on Artjom's answer. use 'des-ede3' if you have a 24byte key.
internals.decrypt = function (message, key) {
var message = Buffer.from(message, 'base64');
var decipher = crypto.createDecipher('des-ede', key);
var decryptedMessage = decipher.update(message, 'hex', 'utf8');
decryptedMessage += decipher.final('utf8');
return decryptedMessage.toString();
}
I am trying to SCP from a remote host using JSch. I have successfully copied file form remote host in java STS IDE. However, when I tried to run it in groovy script which I am executing using SOAP UI, I get following error.
Fri Jun 07 16:53:02 IST 2013:INFO:Exception : groovy.lang.MissingMethodException: No signature of method: java.lang.Byte.minus() is applicable for argument types: (java.lang.String) values: [0]
Possible solutions: minus(java.lang.Number), minus(java.lang.Character), plus(java.lang.String), plus(java.lang.Character), plus(java.lang.Number), times(groovy.lang.Closure)
My code is:
import com.jcraft.jsch.*;
import java.io.*;
import java.lang.*;
FileOutputStream fos=null;
try{
String user="soapui";
String host="192.168.1.1";
String rfile="//bin//output.txt";
String lfile="E://temp1.txt";
String prefix=null;
if(new File(lfile).isDirectory()){
prefix=lfile+File.separator;
}
JSch jsch=new JSch();
Session session=jsch.getSession(user, host, 22);
// username and password will be given via UserInfo interface.
UserInfo ui=new MyUserInfo();
session.setUserInfo(ui);
session.connect();
// exec 'scp -f rfile' remotely
String command="scp -f "+rfile;
Channel channel=session.openChannel("exec");
((ChannelExec)channel).setCommand(command);
// get I/O streams for remote scp
OutputStream out=channel.getOutputStream();
InputStream ins=channel.getInputStream();
channel.connect();
byte[] buf=new byte[10240];
// send '\0'
buf[0]=0; out.write(buf, 0, 1); out.flush();
while(true){
int c=checkAck(ins);
if(c!='C'){
break;
}
// read '0644 '
ins.read(buf, 0, 5);
long filesize=0L;
while(true){
if(ins.read(buf, 0, 1)<0){
// error
break;
}
if(buf[0]==' ')break;
//char c1='0';
//long tmp=Long.valueOf(buf[0]-'0');
**filesize=filesize*10L+(long)(buf[0]-'0');**
}
String file=null;
for(int i=0;;i++){
ins.read(buf, i, 1);
if(buf[i]==(byte)0x0a){
file=new String(buf, 0, i);
break;
}
}
log.info("filesize="+filesize+", file="+file);
// send '\0'
buf[0]=0; out.write(buf, 0, 1); out.flush();
// read a content of lfile
fos=new FileOutputStream(prefix==null ? lfile : prefix+file);
int foo;
while(true){
if(buf.length<filesize) foo=buf.length;
else foo=(int)filesize;
foo=ins.read(buf, 0, foo);
if(foo<0){
// error
break;
}
fos.write(buf, 0, foo);
filesize-=foo;
if(filesize==0L) break;
}
fos.close();
fos=null;
log.info("Closing Stream");
if(checkAck(ins)!=0){
System.exit(0);
}
// send '\0'
buf[0]=0; out.write(buf, 0, 1); out.flush();
}
session.disconnect();
System.exit(0);
}
catch(Exception e){
log.info("Exception : " + e);
try{if(fos!=null)fos.close();}catch(Exception ee){}
}
static int checkAck(InputStream ins) throws IOException{
int b=ins.read();
// b may be 0 for success,
// 1 for error,
// 2 for fatal error,
// -1
if(b==0) return b;
if(b==-1) return b;
if(b==1 || b==2){
StringBuffer sb=new StringBuffer();
int c;
while(c!='\n') {
c=ins.read();
sb.append((char)c);
}
if(b==1){ // error
System.out.print(sb.toString());
}
if(b==2){ // fatal error
System.out.print(sb.toString());
}
}
return b;
}
public class MyUserInfo implements UserInfo{
public String getPassword(){ return passwd;}
public boolean promptYesNo(String str){return true;}
String passwd="123";
public String getPassphrase(){return null;}
public boolean promptPassphrase(String message){return true;}
public boolean promptPassword(String message){return true;}
public void showMessage(String s){};
}
What I have debugged is that on line filesize=filesize*10L+(long)(buf[0]-'0'); of code, groovy fails and generates the exception groovy.lang.MissingMethodException: No signature of method: java.lang.Byte.minus(). Please suggest some solution to this issue.
In groovy, '0' is a String constant, not a char or byte. You can't subtract a String from a byte. Convert it to a char or use the first byte:
byte b = 51 // ASCII '3'
assert b - ('0' as char) == 3
assert b - '0'.bytes[0] == 3
A better way to get a digit from a byte is to use Character.digit(). For example:
filesize = filesize * 10L + Character.digit(buf[0], 10)