【问题标题】:Java/Android - Decrypting AES/CCM with a 4-byte MICJava/Android - 使用 4 字节 MIC 解密 AES/CCM
【发布时间】:2017-09-26 17:28:04
【问题描述】:

我正在尝试解密一些由蓝牙模块加密的测试数据。蓝牙的固件是用 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 字节,但 MAC 为 8 字节。 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

下面是我的输出图像:

下面是 C 输出的图像。

BLE 广告包

编辑:我正在使用选定的答案,但也请查看 Matthew Beckler 的答案。它将提供更深入的答案,并防止以后出现错误。

【问题讨论】:

  • 对于蓝牙,MIC 是通过 3 个额外的字节计算的——0x00、0x01 和一个从 PDU 标头的第一个字节计算的字节。没有那个字节,你就无法获得相同的 MIC。
  • @JamesKPolk 你如何建议我来回传递数据?几天来我一直在寻找解决方案。如果您能进一步帮助我,我将不胜感激。
  • 首先我要弄清楚MIC是如何产生的/
  • 你想加密什么?蓝牙在这里的作用是什么?您想解密通过配对或任意数据加密的 BLE 数据包,并且您碰巧能够重用 BLE 芯片中的 CCM 引擎吗? MIC 不是解密算法的输入,而是当接收方根据随机数、密钥和 AAD 验证接收到的数据时应该匹配的签名。

标签: java android encryption bluetooth aes


【解决方案1】:

以下 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)强制为零。剩下的部分我还在想办法。

【讨论】:

  • 虽然这次 0x01 是标头的第一个字节,但我们是否应该假设总是如此?
  • @FoxDonut:不,我认为有三个可能的值,1、2 和 3。但是规范相当混乱。您有权访问标题吗?
  • 我的工程师控制着代码的 C 部分。我会要求他把他能发给我的任何东西都寄给我。有什么我应该特别要求的吗?
  • 我不确定它是否有帮助,但我添加了一张广告包的图片。我删除了所有机密信息,但你会明白的。我打开了工程师的文件,你只能看到“广告数据 0”部分的前 3 行,然后是完整的“广告数据 1”部分。我不知道如何绕过他的 IDE,但能够获得这么多。让我知道这是否有帮助。
【解决方案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”。这意味着 ADATA 字节是 0、1、2 或 3 中的一个,基于数据通道 PDU 标头的两个 LLID 位(对于任何正在解密有效负载的设备都应该可用)。

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() 调用的字节。

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

【讨论】:

  • 感谢马特的帖子。我目前使用上面的答案让它工作,但我会看看我是否可以从广告包中拉出那一点,所以如果有任何变化,那么它仍然可以工作!很棒的发现!
猜你喜欢
  • 1970-01-01
  • 2012-05-10
  • 2013-04-08
  • 2017-06-16
  • 1970-01-01
  • 2016-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多