/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.sun.javacard.samples.transit;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;
/**
* This applet implements the on-card part of a transit system solution. The
* on-card applet and the off-card applications (transit terminal and POS
* terminal) use a mutual authentication scheme based on a dynamically generated
* DES session key to ensure data integrity and origin authentication during a
* session.
*
* When interacting with a POS terminal, the account maintained on the card can
* be credited or queried for the current balance.
*
* When interacting with a transit terminal, the transit system entry and the
* exit events are checked for consistency and processed - the account
* maintained on the card is debited upon proper exit from the transit system.
*
* Design notes:
* - This sample transit applet does not account for any admin or self-admin use cases such as
* resetting the card of a transit system user when it is in an inconsistent transit
* state. Such an inconsistent state can, for example, result from the user jumping the gates when
* the turnstile is out of order...
* - This sample transit applet does not account for any system-wide transactional
* operations. For example, during a credit operation, if the user removes his card
* just after the balance has been updated but before the APDU response gets to
* the terminal, the account on the card will remain credited but the terminal will
* only be able to detect an IO error b/w the card and the card reader.
* - The constants defined for this class should have been shared through
* an additional class or interface with the terminal code
* (see com.sun.javacard.clientsamples.transit.Constants).
* - This applet could be refactored so that the mutual authentication code
* be moved in a base abstract class and the transit system specific behavior be
* implemented in a subclass of this base class. This refactoring would facilitate
* the reuse of the mutual authentication scheme in other application domain.
*/
public class TransitApplet extends Applet {
// Codes of INS byte in the command APDU header
/**
* INS value for ISO 7816-4 VERIFY command
*/
final static byte VERIFY = (byte) 0x20;
/**
* INS value for INITIALIZE_SESSION command
*/
final static byte INITIALIZE_SESSION = (byte) 0x30;
/**
* INS value for PROCESS_REQUEST command
*/
final static byte PROCESS_REQUEST = (byte) 0x40;
// Tags for TLV records in PROCESS_REQUEST C-APDU
/**
* TLV Tag for PROCESS_ENTRY request
*/
final static byte PROCESS_ENTRY = (byte) 0xC1;
/**
* TLV Tag for PROCESS_EXIT request
*/
final static byte PROCESS_EXIT = (byte) 0xC2;
/**
* TLV Tag for CREDIT request
*/
final static byte CREDIT = (byte) 0xC3;
/**
* TLV Tag for GET_BALANCE request
*/
final static byte GET_BALANCE = (byte) 0xC4;
// Offsets of TLV components in PROCESS_REQUEST C-APDU [CLA, INS, P1, P2, LC
// T L V...]
/**
* TLV tag offset
*/
final static short TLV_TAG_OFFSET = ISO7816.OFFSET_CDATA;
/**
* TLV length offset
*/
final static short TLV_LENGTH_OFFSET = TLV_TAG_OFFSET + 1;
/**
* TLV value offset
*/
final static short TLV_VALUE_OFFSET = TLV_LENGTH_OFFSET + 1;
/**
* Maximum allowed balance
*/
final static short MAX_BALANCE = (short) 500;
/**
* Minimum balance to start transit
*/
final static short MIN_TRANSIT_BALANCE = (short) 10;
/**
* Maximum amount to be credited
*/
final static short MAX_CREDIT_AMOUNT = (short) 100;
/**
* Maximum number of incorrect tries before the PIN is blocked
*/
final static byte MAX_PIN_TRIES = (byte) 0x03;
/**
* Maximum PIN size
*/
final static byte MAX_PIN_SIZE = (byte) 0x08;
/**
* SW bytes for PIN verification failure
*/
final static short SW_VERIFICATION_FAILED = 0x6300;
/**
* SW bytes for PIN validation required
*/
final static short SW_PIN_VERIFICATION_REQUIRED = 0x6301;
/**
* SW bytes for invalid credit amount (amount > MAX_CREDIT_AMOUNT or amount <
* 0)
*/
final static short SW_INVALID_TRANSACTION_AMOUNT = 0x6A83;
/**
* SW bytes for maximum balance exceeded
*/
final static short SW_EXCEED_MAXIMUM_BALANCE = 0x6A84;
/**
* SW bytes for negative balance reached
*/
final static short SW_NEGATIVE_BALANCE = 0x6A85;
/**
* SW bytes for wrong signature condition
*/
final static short SW_WRONG_SIGNATURE = (short) 0x9105;
/**
* SW bytes for minimum transit balance not met
*/
final static short SW_MIN_TRANSIT_BALANCE = (short) 0x9106;
/**
* SW bytes for invalid transit state
*/
final static short SW_INVALID_TRANSIT_STATE = (short) 0x9107;
/**
* SW bytes for success, used in MAC
*/
final static short SW_SUCCESS = (short) 0x9000;
/**
* Unique ID length
*/
final static short UID_LENGTH = (short) 8;
/**
* DES key length in bytes
*/
final static short LENGTH_DES_BYTE = (short) (KeyBuilder.LENGTH_DES / 8);
/**
* Host and card challenge length (note: (2 * CHALLENGE_LENGTH) * 8 ==
* KeyBuilder.LENGTH_DES
*/
final static short CHALLENGE_LENGTH = (short) 4;
/**
* MAC length as generated by Signature.ALG_DES_MAC8_ISO9797_M2
*/
final static short MAC_LENGTH = (short) 8;
/**
* Unique ID
*/
private byte[] uid;
// Signature/key objects
/**
* Cipher used to encrypt - using the static DES key - the derivation data
* to form the session key
*/
private Cipher cipher;
/**
* DES static key, shared b/w host and card
*/
private DESKey staticKey;
/**
* 4-bytes Card challenge
*/
private byte[] cardChallenge; // Transient
/**
* 8-bytes key derivation data, generated from the host challenge and the
* card challenge
*/
private byte[] keyDerivationData; // Transient
/**
* 8-bytes session key data, generated from the derivation data
*/
private byte[] sessionKeyData; // Transient
/**
* DES session key, generated from the derivation data
*/
private DESKey sessionKey; // Transient key
/**
* Indicates whether or not to use transient session key - for performance
* measurement only
*/
private boolean useTransientKey = true;
/**
* Signature initialized with the DES key and used to verify incoming
* messages and to sign outgoing messages
*/
private Signature signature;
/**
* Random data generator, used to generate the card challenge
*/
private RandomData random;
/**
* The user PIN
*/
private OwnerPIN pin;
/**
* The balance
*/
private short balance = (short) 0;
/**
* The entry ststion id, set to (-1) when not in transit
*/
private short entryStationId = (short) -1;
/**
* A correlation id that may be used by the backend system to correlate
* entry and exit events
*/
private byte correlationId = (byte) 0;
/**
* Creates a new Transit applet instance.
*
* @param bArray
* The array containing installation parameters
* @param bOffset
* The starting offset in bArray
* @param bLength
* The length in bytes of the parameter data in bArray
*/
protected TransitApplet(byte[] bArray, short bOffset, byte bLength) {
// Create static DES key
staticKey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
KeyBuilder.LENGTH_DES, false);
// Create cipher
cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false);
// Create card challenge transient buffer
cardChallenge = JCSystem.makeTransientByteArray(CHALLENGE_LENGTH,
JCSystem.CLEAR_ON_DESELECT);
// Create key derivation data transient buffer
keyDerivationData = JCSystem.makeTransientByteArray(
(short) (2 * CHALLENGE_LENGTH), JCSystem.CLEAR_ON_DESELECT);
// Create session key data transient buffer
sessionKeyData = JCSystem.makeTransientByteArray(
(short) (2 * keyDerivationData.length),
JCSystem.CLEAR_ON_DESELECT);
// XXX: Allocates more than actual key to contain the complete
// encrypted key derivation data
// Create signature
signature = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2,
false);
byte aidLen = bArray[bOffset]; // aid length
if (aidLen == (byte) 0) {
register();
} else {
register(bArray, (short) (bOffset + 1), aidLen);
}
// Ignore control info
bOffset = (short) (bOffset + aidLen + 1);
byte infoLen = bArray[bOffset]; // control info length
bOffset = (short) (bOffset + infoLen + 1);
byte paramLen = bArray[bOffset++]; // applet parameters length
// Retrieve UID, static key data and the PIN initialization values from
// installation parameters
if (paramLen <= (LENGTH_DES_BYTE + UID_LENGTH)
|| paramLen > (LENGTH_DES_BYTE + UID_LENGTH + MAX_PIN_SIZE)) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Retrieve the UID
uid = new byte[UID_LENGTH];
Util.arrayCopy(bArray, bOffset, uid, (short) 0, UID_LENGTH);
bOffset += UID_LENGTH;
// Retrieve the static key data
staticKey.setKey(fixParity(bArray, bOffset, LENGTH_DES_BYTE), bOffset);
bOffset += LENGTH_DES_BYTE;
// Retrieve the flag indicating whether or not to use a transient key
useTransientKey = (bArray[bOffset] != (byte) 0);
bOffset++;
// Retrieve the PIN
pin = new OwnerPIN(MAX_PIN_TRIES, MAX_PIN_SIZE);
pin.update(bArray, bOffset,
(byte) (paramLen - UID_LENGTH - LENGTH_DES_BYTE - 1));
// Create transient DES session key
if (useTransientKey) {
sessionKey = (DESKey) KeyBuilder.buildKey(
KeyBuilder.TYPE_DES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_DES,
false);
} else {
sessionKey = (DESKey) KeyBuilder.buildKey(
KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES,
false);
}
// Create and initialize the ramdom data generator with the UID (seed)
random = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
random.setSeed(uid, (short) 0, UID_LENGTH);
// Initialize the cipher with the static key
cipher.init(staticKey, Cipher.MODE_ENCRYPT);
}
public static void install(byte[] bArray, short bOffset, byte bLength) {
// Create a Transit applet instance
new TransitApplet(bArray, bOffset, bLength);
}
public boolean select() {
// The applet declines to be selected
// if the PIN is blocked.
if (pin.getTriesRemaining() == 0) {
return false;
}
return true;
}
public void deselect() {
// Reset the PIN value
pin.reset();
if (!useTransientKey) {
sessionKey.clearKey();
}
}
public void process(APDU apdu) {
// C-APDU: [CLA, INS, P1, P2, LC, ...]
byte[] buffer = apdu.getBuffer();
// Dispatch C-APDU for processing
if (!apdu.isISOInterindustryCLA()) {
switch (buffer[ISO7816.OFFSET_INS]) {
case INITIALIZE_SESSION:
initializeSession(apdu);
return;
case PROCESS_REQUEST:
processRequest(apdu);
return;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
} else {
if (buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)) {
return;
} else if (buffer[ISO7816.OFFSET_INS] == VERIFY) {
verify(apdu);
} else {
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
}
/**
* Initializes a CAD/card interaction session. This is the first step of
* mutual authentication. A new card challenge is generated and used along
* with the passed-in host challenge to generate the derivation data from
* which a new session key is derived. The card challenge is appended to the
* response message. The response message is signed using the newly
* generated session key then sent back. Note that mutual authentication is
* subsequently completed upon succesful verification of the signature of
* the first request received.
*
* @param apdu
* The APDU
*/
private void initializeSession(APDU apdu) {
// C-APDU: [CLA, INS, P1, P2, LC, [4-bytes Host Challenge]]
byte[] buffer = apdu.getBuffer();
if ((buffer[ISO7816.OFFSET_P1] != 0)
|| (buffer[ISO7816.OFFSET_P2] != 0)) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
byte numBytes = buffer[ISO7816.OFFSET_LC];
byte count = (byte) apdu.setIncomingAndReceive();
if (numBytes != CHALLENGE_LENGTH || count != CHALLENGE_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Generate card challenge
generateCardChallenge();
// Generate key derivation data from host challenge and card challenge
generateKeyDerivationData(buffer);
// Generate session key from derivation data
generateSessionKey();
// R-APDU: [[4-bytes Card Challenge], [2-bytes Status Word], [8-bytes
// MAC]]
short offset = 0;
// Append card challenge to response message
offset = Util.arrayCopyNonAtomic(cardChallenge, (short) 0, buffer,
offset, CHALLENGE_LENGTH);
// Append status word to response message
offset = Util.setShort(buffer, offset, SW_SUCCESS);
// Sign response message and append MAC to response message
offset = generateMAC(buffer, offset);
// Send R-APDU
apdu.setOutgoingAndSend((short) 0, offset);
}
/**
* Processes an incoming request. The request message signature is verified,
* then it is dispatched to the relevant handling method. The response
* message is then signed and sent back.
*
* @param apdu
* The APDU
*/
private void processRequest(APDU apdu) {
// C-APDU: [CLA, INS, P1, P2, LC, [Request Message], [8-bytes MAC]]
// Request Message: [T, L, [V...]]
byte[] buffer = apdu.getBuffer();
if ((buffer[ISO7816.OFFSET_P1] != 0)
|| (buffer[ISO7816.OFFSET_P2] != 0)) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
byte numBytes = buffer[ISO7816.OFFSET_LC];
byte count = (byte) apdu.setIncomingAndReceive();
if (numBytes != count) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Check request message signature
if (!checkMAC(buffer)) {
ISOException.throwIt(SW_WRONG_SIGNATURE);
}
if ((numBytes - MAC_LENGTH) != (buffer[TLV_LENGTH_OFFSET] + 2)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
// R-APDU: [[Response Message], [2-bytes Status Word], [8-bytes MAC]]
short offset = 0;
// Dispatch request message for processing
switch (buffer[TLV_TAG_OFFSET]) {
case PROCESS_ENTRY:
offset = processEntry(buffer, TLV_VALUE_OFFSET,
buffer[TLV_LENGTH_OFFSET]);
break;
case PROCESS_EXIT:
offset = processExit(buffer, TLV_VALUE_OFFSET,
buffer[TLV_LENGTH_OFFSET]);
break;
case CREDIT:
offset = credit(buffer, TLV_VALUE_OFFSET, buffer[TLV_LENGTH_OFFSET]);
break;
case GET_BALANCE:
offset = getBalance(buffer, TLV_VALUE_OFFSET,
buffer[TLV_LENGTH_OFFSET]);
break;
default:
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
// Append status word to response message
offset = Util.setShort(buffer, offset, SW_SUCCESS);
// Sign response message and append MAC to response message
offset = generateMAC(buffer, offset);
// Send R-APDU
apdu.setOutgoingAndSend((short) 0, offset);
}
/**
* Verifies the PIN.
*
* @param apdu
* The APDU
*/
private void verify(APDU apdu) {
byte[] buffer = apdu.getBuffer();
byte numBytes = buffer[ISO7816.OFFSET_LC];
byte count = (byte) apdu.setIncomingAndReceive();
if (numBytes != count) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Verify PIN
if (pin.check(buffer, ISO7816.OFFSET_CDATA, numBytes) == false) {
ISOException.throwIt(SW_VERIFICATION_FAILED);
}
}
/**
* Generates a new random card challenge.
*
*/
private void generateCardChallenge() {
// Generate random card challenge
random.generateData(cardChallenge, (short) 0, CHALLENGE_LENGTH);
}
/**
* Generates the session key derivation data from the passed-in host
* challenge and the card challenge.
*
* @param buffer
* The APDU buffer
*/
private void generateKeyDerivationData(byte[] buffer) {
byte numBytes = buffer[ISO7816.OFFSET_LC];
if (numBytes < CHALLENGE_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Derivation data: [[8-bytes host challenge], [8-bytes card challenge]]
// Append host challenge (from buffer) to derivation data
Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, keyDerivationData,
(short) 0, CHALLENGE_LENGTH);
// Append card challenge to derivation data
Util.arrayCopy(cardChallenge, (short) 0, keyDerivationData,
CHALLENGE_LENGTH, CHALLENGE_LENGTH);
}
/**
* Generates a new DES session key from the derivation data.
*
*/
private void generateSessionKey() {
cipher.doFinal(keyDerivationData, (short) 0, (short) keyDerivationData.length,
sessionKeyData, (short) 0);
// Generate new session key from encrypted derivation data
sessionKey.setKey(fixParity(sessionKeyData, (short) 0, (short) sessionKeyData.length /*LENGTH_DES_BYTE*/), (short) 0);
}
/**
* Checks the request message signature.
*
* @param buffer
* The APDU buffer
* @return true if the message signature is correct; false otherwise
*/
private boolean checkMAC(byte[] buffer) {
byte numBytes = buffer[ISO7816.OFFSET_LC];
if (numBytes <= MAC_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Initialize signature with current session key for verification
signature.init(sessionKey, Signature.MODE_VERIFY);
// Verify request message signature
return signature.verify(buffer, ISO7816.OFFSET_CDATA,
(short) (numBytes - MAC_LENGTH), buffer,
(short) (ISO7816.OFFSET_CDATA + numBytes - MAC_LENGTH),
MAC_LENGTH);
}
/**
* Generates the response message MAC: generates the MAC and appends the MAC
* to the response message.
*
* @param buffer
* The APDU buffer
* @param offset
* The offset of the MAC in the buffer
* @return The resulting length of the response message
*/
private short generateMAC(byte[] buffer, short offset) {
// Initialize signature with current session key for signing
signature.init(sessionKey, Signature.MODE_SIGN);
// Sign response message and append the MAC to the response message
short sigLength = signature.sign(buffer, (short) 0, offset, buffer,
offset);
return (short) (offset + sigLength);
}
/**
* Processes a transit entry event. The passed-in entry station ID is
* recorded and the correlation ID is incremented. The UID and the
* correlation ID are returned in the response message.
*
* Request Message: [2-bytes Entry Station ID]
*
* Response Message: [[2-bytes UID], [2-bytes Correlation ID]]
*
* @param buffer
* The APDU buffer
* @param messageOffset
* The offset of the request message content in the APDU buffer
* @param messageLength
* The length of the request message content.
* @return The offset at which content can be appended to the response
* message
*/
private short processEntry(byte[] buffer, short messageOffset,
short messageLength) {
// Request Message: [2-bytes Entry Station ID]
if (messageLength != 2) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Check minimum balance
if (balance < MIN_TRANSIT_BALANCE) {
ISOException.throwIt(SW_MIN_TRANSIT_BALANCE);
}
// Check consistent transit state: should not currently be in transit
if (entryStationId >= 0) {
ISOException.throwIt(SW_INVALID_TRANSIT_STATE);
}
JCSystem.beginTransaction();
// Get/assign entry station ID from request message
entryStationId = Util.getShort(buffer, messageOffset);
// Increment correlation ID
correlationId++;
JCSystem.commitTransaction();
// Response Message: [[8-bytes UID], [2-bytes Correlation ID]]
short offset = 0;
// Append UID to response message
offset = Util.arrayCopy(uid, (short) 0, buffer, offset, UID_LENGTH);
// Append correlation ID to response message
offset = Util.setShort(buffer, offset, correlationId);
return offset;
}
/**
* Processes a transit exit event. The passed-in transit fee is debited from
* the account. The UID and the correlation ID are returned in the response
* message.
*
* Request Message: [1-byte Transit Fee]
*
* Response Message: [[2-bytes UID], [2-bytes Correlation ID]]
*
* @param buffer
* The APDU buffer
* @param messageOffset
* The offset of the request message content in the APDU buffer
* @param messageLength
* The length of the request message content.
* @return The offset at which content can be appended to the response
* message
*/
private short processExit(byte[] buffer, short messageOffset,
short messageLength) {
// Request Message: [1-byte Transit Fee]
if (messageLength != 1) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Check minimum balance
if (balance < MIN_TRANSIT_BALANCE) {
ISOException.throwIt(SW_MIN_TRANSIT_BALANCE);
}
// Check consistent transit state: should be currently in transit
if (entryStationId < 0) {
ISOException.throwIt(SW_INVALID_TRANSIT_STATE);
}
// Get transit fee from request message
byte transitFee = buffer[messageOffset];
// Check potential negative balance
if (balance < transitFee) {
ISOException.throwIt(SW_NEGATIVE_BALANCE);
}
JCSystem.beginTransaction();
// Debit transit fee
balance -= transitFee;
// Reset entry station ID
entryStationId = -1;
JCSystem.commitTransaction();
// Response Message: [[8-bytes UID], [2-bytes Correlation ID]]
short offset = 0;
// Append UID to response message
offset = Util.arrayCopy(uid, (short) 0, buffer, offset, UID_LENGTH);
// Append correlation ID to response message
offset = Util.setShort(buffer, offset, correlationId);
return offset;
}
/**
* Credits the account of the passed-in amount.
*
* Request Message: [1-byte Credit Amount]
*
* Response Message: []
*
* @param buffer
* The APDU buffer
* @param messageOffset
* The offset of the request message content in the APDU buffer
* @param messageLength
* The length of the request message content.
* @return The offset at which content can be appended to the response
* message
*/
private short credit(byte[] buffer, short messageOffset, short messageLength) {
// Check access authorization
if (!pin.isValidated()) {
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
}
// Request Message: [1-byte Credit Amount]
if (messageLength != 1) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Get credit amount from request message
byte creditAmount = buffer[messageOffset];
// Check credit amount
if ((creditAmount > MAX_CREDIT_AMOUNT) || (creditAmount < 0)) {
ISOException.throwIt(SW_INVALID_TRANSACTION_AMOUNT);
}
// Check the new balance
if ((short) (balance + creditAmount) > MAX_BALANCE) {
ISOException.throwIt(SW_EXCEED_MAXIMUM_BALANCE);
}
// Credit the amount
balance += creditAmount;
// Response Message: []
return 0;
}
/**
* Gets/returns the balance.
*
* Request Message: []
*
* Response Message: [2-bytes Balance]
*
* @param buffer
* The APDU buffer
* @param messageOffset
* The offset of the request message content in the APDU buffer
* @param messageLength
* The length of the request message content.
* @return The offset at which content can be appended to the response
* message
*/
private short getBalance(byte[] buffer, short messageOffset,
short messageLength) {
// Check access authorization
if (!pin.isValidated()) {
ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
}
// Request Message: []
if (messageLength != 0) {
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
}
// Response Message: [2-bytes Balance]
short offset = 0;
// Append balance to response message
offset = Util.setShort(buffer, offset, balance);
return offset;
}
/**
* Fixes the parity on DES key data.
*
* @param buffer
* The buffer containing the DES key data
* @param offset
* The offset of the DES key data in the buffer
* @param messageLength
* The length of the DES key data
* @return The passed-in buffer with the DES key data parity fixed
*/
private byte[] fixParity(byte[] buffer, short offset, short length) {
for (byte i = 0; i < length; i++) {
short parity = 0;
buffer[(short) (offset + i)] &= 0xFE;
for (byte j = 1; j < 8; j++) {
if ((buffer[(short) (offset + i)] & (byte) (1 << j)) != 0) {
parity++;
}
}
if ((parity % 2) == 0) {
buffer[(short) (offset + i)] |= 1;
}
}
return buffer;
}
}
|