Java / Android - 使用4字节MIC解密AES / CCM

时间:2017-09-26 17:28:05

标签: java android encryption bluetooth aes

我正在尝试解密由蓝牙模块加密的一些测试数据。如果重要的话,蓝牙的固件用C编程。

加密的数据是:

// Test Bytes - 16 bytes
byte[] testInput = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

// Test key - 16 bytes, 128-bit
byte[] keyBytes = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                   0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

// Test nonce - 13 bytes, 104-bit
byte[] nonce = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
                0x0a,0x0b,0x0c};

这就是问题所在。使用AES / CCM加密C中的数据,产生16字节的输出,具有4字节的MIC。当我使用Java中的AES / CCM / NoPadding加密数据时,输出也是16字节,但具有8字节MAC。似乎MAC和MIC这两个术语含糊不清,其中MIC用于蓝牙术语。

当我在Java中加密上述testInput时,我获得与C编程加密相同的16字节输出。但是,由于MIC和MAC的长度不同,我无法解密两端的数据。

有解决方法吗?

我添加了我的Java代码:

Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(testInput);

// The first 16 bytes print out equivalently with the C-language AES/CCM

以下是我输出的图片:

enter image description here

下面是C输出的图像。

enter image description here

BLE广告包

enter image description here

编辑:我正在使用所选答案,但请查看Matthew Beckler的答案。它将为答案提供更多深度,并在以后防止错误。

2 个答案:

答案 0 :(得分:2)

以下Java代码将生成与C代码相同的输出:

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Security;

public class Main {

    // Test Bytes - 16 bytes
    static byte[] testInput = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

    // Test key - 16 bytes, 128-bit
    static byte[] keyBytes = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

    // Test nonce - 13 bytes, 104-bit
    static byte[] nonce = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
            0x0a, 0x0b, 0x0c};


    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        GCMParameterSpec parameterSpec = new GCMParameterSpec(32, nonce);
        Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, parameterSpec);
        cipher.updateAAD(new byte[]{0x01});
        System.out.println(DatatypeConverter.printHexBinary(cipher.doFinal(testInput)));
    }
}

但是,我不确定如何找到要提供给Cipher.updateAAD()的字节。通过反复试验找到了0x01。试图阅读蓝牙4.0规范是非常痛苦的。该规范似乎表明该字节是数据包标头的第一个字节,其中3个位(NESN,SN,MD)被强制为零。我还想弄清楚剩下的一点。

答案 1 :(得分:2)

我已经弄清楚了如何使用计算机上的软件加密库来复制BLE风格的AES CCM。此处有更多详细信息(https://devzone.nordicsemi.com/f/nordic-q-a/28524/aes-ccm-encryption-by-s132-timeslot-api/137155#137155),但问题的症结在于 BLE CCM指定了一个字节的ADATA(“附加数据”),该字节包含在MAC计算中,但未作为加密的一部分进行加密。 BLE规范规定,该字节必须等于“数据通道PDU标头的第一个八位字节,其中NESN,SN和MD位被掩码为0”。这意味着,根据数据通道PDU标头的两个LLID位(无论哪个设备解密有效负载,ADATA字节都是0、1、2或3之一)。

BLE规范提供了LLID值的以下描述:

  • 00b =保留供将来使用
  • 01b = LL数据PDU:L2CAP消息或空PDU的连续片段
  • 10b = LL数据PDU:L2CAP消息的开始或没有碎片的完整L2CAP消息
  • 11b = LL控制PDU

您似乎已经完成了cipher.updateAAD(new byte[]{0x01});的99%的定位工作。您应该能够从数据包结构中较早的某个位置提取该0x01值。您可能需要屏蔽掉包字节的某些位(& 0xE3会这样做),以找到用于cipher.updateAAD()调用的正确字节。

希望这会有所帮助,如果您需要更多详细信息,我很乐意回答问题。