C# 中的安全消息传递实现

问题描述 投票:0回答:1

建立 PACE PIN 身份验证后,我尝试实现安全消息传递以便能够发送安全 APDU 命令。我的实施基于 ICAO 9303 第 11 部分手册,第 9.8 章,第 63 页:https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf,

以及我使用的Java代码:https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol /pace/SecureMessaging.java,以及来自已实现安全消息传递的 PlatinumReader 的日志。 这是我的代码: APDU的计算方法

 public string chipherAPDU(string apdu, string keyEnc, string keyMac)
   {
       string chipherApdu = "";

       byte[] apduBytes = StringToByteArray(apdu);
       byte[] KeyENC = StringToByteArray(keyEnc);
       byte[] KeyMAC = StringToByteArray(keyMac);
       byte[] secureMessagingSSC = StringToByteArray("00000000000000000000000000000001");
       SecureMessaging sm = new SecureMessaging(KeyMAC, KeyENC);
       chipherApdu = byteToHexStr ( sm.encrypt(apduBytes, secureMessagingSSC) );
       return chipherApdu;
   }

安全消息传递类:

  internal class SecureMessaging
  {

          private readonly byte[] NULL = new byte[] { 0x00 };
          private const byte PAD = 0x80;
          private readonly byte[] secureMessagingSSC;
          private readonly byte[] keyMAC;
          private readonly byte[] keyENC;           

          public SecureMessaging(byte[] keyMAC, byte[] keyENC)
          {
              this.keyENC = keyENC;
              this.keyMAC = keyMAC;

              secureMessagingSSC = new byte[16];
          }

          public byte[] encrypt(byte[] apdu)
          {
              incrementSSC(secureMessagingSSC);
              byte[] commandAPDU = encrypt(apdu, secureMessagingSSC);
              incrementSSC(secureMessagingSSC);

              return commandAPDU;
          }

          public byte[] encrypt(byte[] apdu, byte[] secureMessagingSSC)
          {
              MemoryStream outputStream = new MemoryStream();
              CardCommandAPDU cAPDU = new CardCommandAPDU(apdu);

              if (cAPDU.isSecureMessaging())
              {
                  throw new ArgumentException("Malformed APDU.");
              }

              byte[] data = cAPDU.data;
              byte[] header = cAPDU.header;
              int lc = cAPDU.lc;
              int le = cAPDU.le;

              if (data.Length > 0)
              {
                  data = pad(data, 16);
                  using (Aes aes = Aes.Create())
                  {
                      aes.Key = keyENC;
                      aes.IV = getCipherIV(secureMessagingSSC);
                      aes.Mode = CipherMode.CBC;
                      aes.Padding = PaddingMode.None;

                      ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
                      byte[] encryptedData = encryptor.TransformFinalBlock(data, 0, data.Length);

                      outputStream.Write(new byte[] { 0x01 }, 0, 1);
                      outputStream.Write(encryptedData, 0, encryptedData.Length);
                  }
              }

              return outputStream.ToArray();
          }

          public byte[] decrypt(byte[] response)
          {
              if (response.Length < 12)
              {
                  throw new ArgumentException("Malformed Secure Messaging APDU.");
              }
              return decrypt(response, secureMessagingSSC);
          }

          private byte[] decrypt(byte[] response, byte[] secureMessagingSSC)
          {
              MemoryStream outputStream = new MemoryStream();
              byte[] statusBytes = new byte[2];
              byte[] dataObject = null;
              byte[] macObject = new byte[8];                

              return outputStream.ToArray();
          }

          public static void incrementSSC(byte[] ssc)
          {
              for (int i = ssc.Length - 1; i >= 0; i--)
              {
                  ssc[i]++;
                  if (ssc[i] != 0)
                  {
                      break;
                  }
              }
          }

          // Other methods omitted for brevity
          private byte[] pad(byte[] data, int blockSize)
          {
              byte[] result = new byte[data.Length + (blockSize - data.Length % blockSize)];
              Array.Copy(data, 0, result, 0, data.Length);
              result[data.Length] = PAD;

              return result;
          }

          private byte[] getCipherIV(byte[] smssc)
          {
              using (Aes aes = Aes.Create())
              {
                  aes.Key = keyENC;
                  aes.Mode = CipherMode.ECB;
                  aes.Padding = PaddingMode.None;

                  ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, null);
                  return encryptor.TransformFinalBlock(smssc, 0, smssc.Length);
              }
          }
  }

类 CardCommandAPDU :

  public class CardCommandAPDU
  {
      public byte[] header = new byte[4];
      public int le = -1;
      public int lc = -1;
      public byte[] data;
      public CardCommandAPDU(byte[] commandAPDU)
      {
          Array.Copy(commandAPDU, 0, header, 0, 4);
          setBody(copy(commandAPDU, 4, commandAPDU.Length - 4));
      }



      /**
       * Définit le corps (LE, DATA, LC) de l'APDU.
       *
       * @param body Corps de l'APDU
       */
      public void setBody(byte[] body)
      {
          /*
           * Cas 1. : |CLA|INS|P1|P2|
           * Cas 2. : |CLA|INS|P1|P2|LE|
           * Cas 2.1: |CLA|INS|P1|P2|EXTLE|
           * Cas 3. : |CLA|INS|P1|P2|LC|DATA|
           * Cas 3.1: |CLA|INS|P1|P2|EXTLC|DATA|
           * Cas 4. : |CLA|INS|P1|P2|LC|DATA|LE|
           * Cas 4.1: |CLA|INS|P1|P2|EXTLC|DATA|LE|
           * Cas 4.2: |CLA|INS|P1|P2|LC|DATA|EXTLE|
           * Cas 4.3: |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
           */
          try
          {
              using (MemoryStream stream = new MemoryStream(body))
              {
                  int length = (int)stream.Length;

                  // Nettoyage
                  lc = -1;
                  le = -1;
                  data = new byte[0];

                  if (length == 0)
                  {
                      // Cas 1. : |CLA|INS|P1|P2|
                  }
                  else if (length == 1)
                  {
                      // Cas 2 |CLA|INS|P1|P2|LE|
                      le = stream.ReadByte() & 0xFF;
                  }
                  else if (length < 65536)
                  {
                      int tmp = stream.ReadByte();

                      if (tmp == 0)
                      {
                          // Cas 2.1, 3.1, 4.1, 4.3
                          if (stream.Length < 3)
                          {
                              // Cas 2.1 |CLA|INS|P1|P2|EXTLE|
                              le = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
                          }
                          else
                          {
                              // Cas 3.1, 4.1, 4.3
                              lc = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;

                              data = new byte[lc];
                              stream.Read(data, 0, lc);

                              if (stream.Length == 1)
                              {
                                  // Cas 4.1 |CLA|INS|P1|P2|EXTLC|DATA|LE|
                                  le = stream.ReadByte() & 0xFF;
                              }
                              else if (stream.Length == 2)
                              {
                                  // Cas 4.3 |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
                                  le = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
                              }
                              else if (stream.Length == 3)
                              {
                                  if (stream.ReadByte() == 0)
                                  {
                                      // Cas 4.3 |CLA|INS|P1|P2|EXTLC|DATA|EXTLE|
                                      le = (stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF;
                                  }
                                  else
                                  {
                                      throw new ArgumentException("APDU malformée.");
                                  }
                              }
                              else if (stream.Length > 3)
                              {
                                  throw new ArgumentException("APDU malformée.");
                              }
                          }
                      }
                      else if (tmp > 0)
                      {
                          // Cas 3, 4, 4.2
                          lc = tmp & 0xFF;
                          data = new byte[lc];
                          stream.Read(data, 0, lc);

                          if (stream.Length == 1)
                          {
                              // Cas 4 |CLA|INS|P1|P2|LC|DATA|LE|
                              setLE((byte)stream.ReadByte());
                          }
                          else if (stream.Length == 3)
                          {
                              // Cas 4.2 |CLA|INS|P1|P2|LC|DATA|EXTLE|
                              stream.ReadByte(); // Ignorer le premier octet
                              setLE((short)((stream.ReadByte() & 0xFF) << 8 | stream.ReadByte() & 0xFF));
                          }
                          else if (stream.Length == 2 || stream.Length > 3)
                          {
                              throw new ArgumentException("APDU malformée.");
                          }
                      }
                      else
                      {
                          throw new ArgumentException("APDU malformée.");
                      }
                  }
                  else
                  {
                      throw new ArgumentException("APDU malformée.");
                  }
              }
          }
          catch (Exception e)
          {
              Console.WriteLine("Exception", e);
          }
      }

      /**
       * Définit le champ de longueur attendue (LE) de l'APDU.
       *
       * @param le Champ de longueur attendue (LE)
       */
      public void setLE(short le)
      {
          if (le == (short)0x0000)
          {
              setLE(65536);
          }
          else
          {
              setLE((int)le & 0xFFFF);
          }
      }

      /**
       * Définit le champ de longueur attendue (LE) de l'APDU.
       *
       * @param le Champ de longueur attendue (LE)
       */
      public void setLE(int le)
      {
          if (le < 0 || le > 65536)
          {
              throw new ArgumentException("La longueur doit être comprise entre '1' et '65535'.");
          }
          else
          {
              this.le = le;
          }
      }

              /**
       * Copie une plage de bytes.
       *
       * @param input l'entrée
       * @param offset l'offset
       * @param length la longueur
       * @return le tableau de bytes
       */
      public static byte[] copy(byte[] input, int offset, int length)
      {
          if (input == null)
          {
              return null;
          }
          byte[] tmp = new byte[length];
          Array.Copy(input, offset, tmp, 0, length);
          return tmp;
      }


      public  bool isSecureMessaging()
      {
          return (header[0] & 0x0F) == 0x0C;
      }

  }

以下是我使用的 PlatinumReader 的数据,它运行良好,我正在尝试获得相同的结果:

APDU: Request: 00 a4 04 0c 07 a0 00 00 02 47 10 01          
GENERIC P7896 T36084 : APDU: SMRequest - 00000000000000000000000000000001
0c a4 04 0c 1d 87 11 01 a9 48 e4 f1 a8 23 56 c1
1c f1 a6 a0 b1 8d 0c 8f 8e 08 71 b6 0b b9 3b 7a 
f1 08 00

这是我使用相同值进行的测试:

 string apduSM = chipherAPDU("00a4040c07a0000002471001", "3d7155f3791c313c2924f51ae60f1ac9" , "b5feb9488f17be03e54c7d80907e8a1f");
  addLogMsg("ADPU SM : " + apduSM);

这是我的结果:

ADPU SM : 01A948E4F1A82356C11CF1A6A0B18D0C8F

使用的密钥是短暂的,因此我可以发布它们,这只是一个测试阶段:

密钥:K_MAC - 派生的 MAC 密钥(K_MAC [PACE]) b5 fe b9 48 8f 17 be 03 e5 4c 7d 80 90 7e 8a 1f

密钥:K_Enc - 派生加密密钥(K_Enc [PACE]) 3d 71 55 f3 79 1c 31 3c 29 24 f5 1a e6 0f 1a c9

这是测试:https://dotnetfiddle.net/l3z7P1

我有部分加密数据,我应该有这样的结构: Header Lc' '87' L '01' 'Encrypted Data' Le' 其中标题是:0c a4 04 0c

c# encryption cryptography apdu ecdh
1个回答
0
投票

C# 代码生成的结果与参考数据之间的差异是由于 Java 参考代码到 C# 的移植不完整。
特别是,缺少 MAC 的实现和执行 ASN.1/DER 编码的

TLV
类的实现。如果将 Java 代码完全移植到 C#,则可以重现参考数据。

在以下 C# 代码中,

encrypt(byte[], byte[])
方法已补充了生成参考数据所需的 Java 代码功能(Java 参考代码的相应行在 C# 代码中指定):

using Org.BouncyCastle.Crypto.Macs;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;

...

public byte[] encrypt(byte[] apdu, byte[] secureMessagingSSC)
{
    CardCommandAPDU cAPDU = new CardCommandAPDU(apdu);
    
    if (cAPDU.isSecureMessaging())
    {
        throw new ArgumentException("Malformed APDU.");
    }
    
    byte[] data = cAPDU.data;
    byte[] header = cAPDU.header;
    int lc = cAPDU.lc;
    int le = cAPDU.le;
    
    byte[] dataEncrypted = [];
    if (data.Length > 0)
    {
        data = pad(data, 16);
        using (Aes aes = Aes.Create())
        {
            aes.Key = keyENC;
            aes.IV = getCipherIV(secureMessagingSSC);
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
    
            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
            dataEncrypted = encryptor.TransformFinalBlock(data, 0, data.Length);
    
            // Add padding indicator 0x01 
            // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L114
            dataEncrypted = [0x01, ..dataEncrypted];
    
            // ASN.1/DER (TLV) - encrypted data
            // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L117
            dataEncrypted = [0x87, (byte)dataEncrypted.Length, ..dataEncrypted];
        }
    }
    
    // Write protected LE: skipped as le < 0
    // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L123
    if (le >= 0)
    {
        // ...
    }
    
    // Indicate Secure Messaging 
    // Java code:   https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L137
    header[0] |= 0x0C;
    
    // Calculate MAC
    // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L142
    byte[] mac = new byte[16];
    CMac cmac = getCMAC(secureMessagingSSC);
    byte[] paddedHeader = pad(header, 16);
    cmac.BlockUpdate(paddedHeader, 0, paddedHeader.Length);
    if (dataEncrypted.Length > 0)
    {
        byte[] paddedData = pad(dataEncrypted, 16);
        cmac.BlockUpdate(paddedData, 0, paddedData.Length);
    }
    cmac.DoFinal(mac, 0);
    mac = mac[..8];
    
    // ASN.1/DER (TLV) - MAC
    // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L160
    mac = [0x8e, (byte)mac.Length, ..mac];
    
    // Concate encrypted data and MAC 
    // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L163 
    byte[] secureData = [..dataEncrypted, ..mac];
    
    // Add header and footer
    // Java code: https://github.com/ecsec/open-ecard/blob/master/ifd/ifd-protocols/pace/src/main/java/org/openecard/ifd/protocol/pace/SecureMessaging.java#L165
    byte[] secureCommand = [..header, (byte)secureData.Length, ..secureData, 0x00];
    
    return secureCommand;
}
        
private CMac getCMAC(byte[] smssc)
{
    CMac cmac = new CMac(new AesEngine());
    cmac.Init(new KeyParameter(keyMAC));
    cmac.BlockUpdate(smssc, 0, smssc.Length);

    return cmac;
}

如果在发布的 C# 代码中替换/添加这些方法,则可以重现参考数据

0x0ca4040c1d871101a948e4f1a82356c11cf1a6a0b18d0c8f8e0871b60bb93b7af10800

请注意,上述更改是不完整,仅实现此特定参考数据所需的功能。为了使 C# 代码能够一般地工作,必须

完全
迁移 Java 代码中的encrypt(byte[], byte[])方法!

© www.soinside.com 2019 - 2024. All rights reserved.