I'm trying to implement this decryption method from java to c#: https://github.com/ecsec/open-ecard/blob/f66ae48e7bbb2bb27a524e12d3febabf162c17c7/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L198C3-L198C90
/**
* Decrypt the APDU.
*
* @param response the response
* @param secureMessagingSSC the secure messaging ssc
* @return the byte[]
* @throws Exception the exception
*/
private byte[] decrypt(byte[] response, byte[] secureMessagingSSC) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(response);
ByteArrayOutputStream baos = new ByteArrayOutputStream(response.length - 10);
// Status bytes of the response APDU. MUST be 2 bytes.
byte[] statusBytes = new byte[2];
// Padding-content indicator followed by cryptogram 0x87.
byte[] dataObject = null;
// Cryptographic checksum 0x8E. MUST be 8 bytes.
byte[] macObject = new byte[8];
/*
* Read APDU structure
* Case 1: DO99|DO8E|SW1SW2
* Case 2: DO87|DO99|DO8E|SW1SW2
* Case 3: DO99|DO8E|SW1SW2
* Case 4: DO87|DO99|DO8E|SW1SW2
*/
byte tag = (byte) bais.read();
// Read data object (OPTIONAL)
if (tag == (byte) 0x87) {
int size = bais.read();
if (size > 0x80) {
byte[] sizeBytes = new byte[size & 0x0F];
bais.read(sizeBytes, 0, sizeBytes.length);
size = new BigInteger(1, sizeBytes).intValue();
}
bais.skip(1); // Skip encryption header
dataObject = new byte[size - 1];
bais.read(dataObject, 0, dataObject.length);
tag = (byte) bais.read();
}
// Read processing status (REQUIRED)
if (tag == (byte) 0x99) {
if (bais.read() == (byte) 0x02) {
bais.read(statusBytes, 0, 2);
tag = (byte) bais.read();
}
} else {
throw new IOException("Malformed Secure Messaging APDU");
}
// Read MAC (REQUIRED)
if (tag == (byte) 0x8E) {
if (bais.read() == (byte) 0x08) {
bais.read(macObject, 0, 8);
}
} else {
throw new IOException("Malformed Secure Messaging APDU");
}
// Only 2 bytes status should remain
if (bais.available() != 2) {
throw new IOException("Malformed Secure Messaging APDU");
}
// Calculate MAC for verification
CMac cmac = getCMAC(secureMessagingSSC);
byte[] mac = new byte[16];
synchronized (cmac) {
ByteArrayOutputStream macData = new ByteArrayOutputStream();
// Write padding-content
if (dataObject != null) {
TLV paddedDataObject = new TLV();
paddedDataObject.setTagNumWithClass((byte) 0x87);
paddedDataObject.setValue(ByteUtils.concatenate((byte) 0x01, dataObject));
macData.write(paddedDataObject.toBER());
}
// Write status bytes
TLV statusBytesObject = new TLV();
statusBytesObject.setTagNumWithClass((byte) 0x99);
statusBytesObject.setValue(statusBytes);
macData.write(statusBytesObject.toBER());
byte[] paddedData = pad(macData.toByteArray(), 16);
cmac.update(paddedData, 0, paddedData.length);
cmac.doFinal(mac, 0);
mac = ByteUtils.copy(mac, 0, 8);
}
// Verify MAC
if (!ByteUtils.compare(mac, macObject)) {
throw new GeneralSecurityException("Secure Messaging MAC verification failed");
}
// Decrypt data
if (dataObject != null) {
Cipher c = getCipher(secureMessagingSSC, Cipher.DECRYPT_MODE);
byte[] data_decrypted = c.doFinal(dataObject);
baos.write(unpad(data_decrypted));
}
// Add status code
baos.write(statusBytes);
return baos.toByteArray();
}
Here is my method i dont know how to implement the commented part:
public byte[] decrypt(byte[] response, byte[] secureMessagingSSC)
{
using (MemoryStream bais = new MemoryStream(response))
using (MemoryStream baos = new MemoryStream(response.Length - 10))
{
byte[] statusBytes = new byte[2];
byte[] dataObject = null;
byte[] macObject = new byte[8];
byte tag = (byte)bais.ReadByte();
if (tag == 0x87)
{
int size = bais.ReadByte();
if (size > 0x80)
{
byte[] sizeBytes = new byte[size & 0x0F];
bais.Read(sizeBytes, 0, sizeBytes.Length);
size = new BigInteger(1, sizeBytes).IntValue;
}
bais.Seek(1, SeekOrigin.Current); // Skip encryption header
dataObject = new byte[size - 1];
bais.Read(dataObject, 0, dataObject.Length);
tag = (byte)bais.ReadByte();
}
if (tag == 0x99)
{
if (bais.ReadByte() == 0x02)
{
bais.Read(statusBytes, 0, 2);
tag = (byte)bais.ReadByte();
}
}
else
{
throw new IOException("Malformed Secure Messaging APDU");
}
if (tag == 0x8E)
{
if (bais.ReadByte() == 0x08)
{
bais.Read(macObject, 0, 8);
}
}
else
{
throw new IOException("Malformed Secure Messaging APDU");
}
// Only 2 bytes status should remain
if (bais.Length - bais.Position != 2)
{
throw new IOException("Malformed Secure Messaging APDU");
}
// Calculate MAC for verification
CMac cmac = getCMAC(secureMessagingSSC);
byte[] mac = new byte[16];
lock (cmac)
{
MemoryStream macData = new MemoryStream();
// Write padding-content
if (dataObject != null)
{
TLV paddedDataObject = new TLV();
paddedDataObject.SetTagNumWithClass(0x87);
paddedDataObject.SetValue(ByteUtils.Concatenate((byte)0x01, dataObject));
macData.Write(paddedDataObject.ToBER(), 0, paddedDataObject.ToBER().Length);
}
// Write status bytes
TLV statusBytesObject = new TLV();
statusBytesObject.SetTagNumWithClass(0x99);
statusBytesObject.SetValue(statusBytes);
macData.Write(statusBytesObject.ToBER(), 0, statusBytesObject.ToBER().Length);
byte[] paddedData = pad(macData.ToArray(), 16);
cmac.BlockUpdate(paddedData, 0, paddedData.Length);
cmac.DoFinal(mac, 0);
mac = ByteUtils.Copy(mac, 0, 8);
}
// Verify MAC
if (!ByteUtils.Compare(mac, macObject))
{
throw new GeneralSecurityException("Secure Messaging MAC verification failed");
}
baos.Write(statusBytes, 0, statusBytes.Length);
return baos.ToArray();
}
}
I'm having always having 900 when i decrypt the encrypted value : You can test the code here : https://dotnetfiddle.net/33V9Bl
There are two issues in the code, one with authentication and one with decryption:
Authentication issue:
Authentication fails because your implementation of the TLV
class and/or the associated classes is flawed. More prcisely, the bug lies in the porting of the enum TagClass
. While enum types can contain static methods in Java, this is not possible in C# (see here). During porting, the static method TagClass.getTagClass()
was simply not ported.
A possible fix is to implement the missing method in the Tag
class, e.g.:
public static TagClass GetTagClass(byte octet)
{
byte classByte = (byte)((octet >> 6) & 0x03);
switch (classByte)
{
case 0: return TagClass.UNIVERSAL;
case 1: return TagClass.APPLICATION;
case 2: return TagClass.CONTEXT;
case 3: return TagClass.PRIVATE;
default: throw new GeneralSecurityException("Unknown tag class: " + classByte);
}
}
Additionally, in method Tag.FromBer()
, replace line:
TagClass tagClass = (TagClass)data[0];
with line:
TagClass tagClass = GetTagClass(data[0]);
With this change, authentication is successful.
Decryption issue:
The missing decryption can be implemented compactly, e.g. with Aes#DecryptCbc()
:
using System.Security.Cryptography;
...
using (Aes aes = Aes.Create())
{
aes.Key = keyENC;
byte[] decyptedPadded = aes.DecryptCbc(dataObject, getCipherIV(secureMessagingSSC), PaddingMode.None);
byte[] decrypted = Unpad(decyptedPadded);
baos.Write(decrypted);
}
With this change, the ciphertext is successfully decrypted.
Edit:
If, as noted in the comment, DecryptCbc()
is not available in the .NET version used, it can alternatively be decrypted as follows:
using (Aes aes = Aes.Create())
{
aes.Key = keyENC;
aes.IV = getCipherIV(secureMessagingSSC);
aes.Padding = PaddingMode.None;
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
byte[] decryptedPadded = decryptor.TransformFinalBlock(dataObject, 0, dataObject.Length);
byte[] decrypted = Unpad(decryptedPadded);
baos.Write(decrypted, 0, decrypted.Length);
}
}