1. AES CCM 概述:CCM模式的定义、应用场景与GCM对比

各位好,我是老周。今天咱们开始聊AES CCM的内存占用问题。不过别急,在深入内存细节之前,得先把CCM模式本身搞清楚。说白了,CCM就是CTR模式加上CBC-MAC——一个负责加密,一个负责认证。我当年第一次看到这个组合时,心里想的是:这不就是把两个模式硬拼在一起吗?后来踩过坑才明白,这里面的门道可不少。

1.1 CCM模式到底是什么?

CCM的全称是Counter with CBC-MAC。它是一种认证加密模式,同时提供机密性和完整性保护。你想想看,很多场景下光加密是不够的——别人虽然看不懂你的数据,但可以篡改它。CCM就是来解决这个问题的。

它的核心结构其实很清晰:

  • 加密部分:使用CTR(计数器)模式,把AES当做一个流密码来用
  • 认证部分:使用CBC-MAC,对明文和附加数据计算一个消息认证码

这里有个关键点:CCM先做认证,后做加密。我习惯叫它「先认证后加密」。为什么是这个顺序?因为CBC-MAC需要完整的明文输入,而CTR模式可以边加密边输出。这个顺序直接影响内存占用——后面我们会详细分析。

核心要点:CCM = CTR + CBC-MAC,先认证后加密。Nonce长度为7-13字节,这是WiFi和蓝牙协议里常见的配置。

1.2 应用场景:WiFi和蓝牙

CCM最常见的两个战场,就是WiFi和蓝牙。我做过几个无线协议栈的移植项目,几乎每次都要跟CCM打交道。

WiFi(IEEE 802.11i/WPA2)

WiFi安全协议里,CCMP(Counter Mode with CBC-MAC Protocol)是强制要求的加密方案。WPA2个人版和企业版都用它。我记得有一次调试WiFi驱动,发现吞吐量上不去,查了半天原来是CCM的Nonce生成逻辑在某个边界条件下出了bug——每次重连都复用同一个Nonce,加密直接废掉。

蓝牙(Bluetooth 4.0+/LE Secure Connections)

蓝牙低功耗(BLE)在4.2版本之后引入了LE Secure Connections,底层用的就是AES-CCM。我做过一个蓝牙门锁的项目,MCU只有64KB的RAM,CCM的内存占用成了大问题。那时候我才真正开始研究CCM的内存布局——每个中间缓冲区、每个状态变量都得精打细算。

其他场景还包括:

  • Zigbee(802.15.4安全套件)
  • IPsec(RFC 4309定义了CCM的使用方式)
  • TLS 1.2(虽然现在主流是GCM,但CCM也是可选套件)

1.3 CCM vs GCM:到底差在哪?

很多人问我:CCM和GCM都是认证加密,为什么选CCM?我一般会反问:你的硬件支持GCM的并行计算吗?

咱们直接看对比表:

对比维度 CCM GCM
加密模式 CTR(计数器模式) CTR(计数器模式)
认证方式 CBC-MAC GHASH(Galois域乘法)
处理顺序 先认证后加密 先加密后认证
并行能力 差(CBC-MAC串行依赖) 好(GHASH可流水线化)
内存占用 较低(但需要额外认证缓冲区) 较高(GHASH表需要预计算)
硬件加速 简单(仅需AES核心) 复杂(需要GF乘法器)
Nonce长度 7-13字节(固定) 12字节(推荐)

从表里能看出几个关键差异:

  • 认证部分:CCM用CBC-MAC,GCM用GHASH。CBC-MAC是串行的,每算一个块都得等前一个块的结果。GHASH虽然也是串行,但它的乘法可以在硬件里流水线化。
  • 内存占用:CCM在认证阶段需要缓存整个明文(或者至少一个完整的认证块),而GCM可以边加密边认证。这个差异在资源受限的MCU上特别明显。
  • Nonce管理:CCM的Nonce长度是可变的(7-13字节),GCM固定12字节。我遇到过有人把Nonce长度设错,结果加密出来的数据别人解不了——这种坑最好在设计阶段就规避。

避坑提醒:我曾经在一个项目里,为了省内存把CCM的Nonce长度设成7字节。结果WiFi协议栈要求Nonce必须12字节,两边对不上,调试了整整两天。所以,Nonce长度一定要跟协议规范对齐,别自己瞎改。

1.4 CCM的工作流程

咱们用一张图把CCM的完整流程画清楚。这张图我画了很多遍,每次给团队新人讲CCM时都会拿出来用。

AES-CCM 工作流程 明文数据 附加认证数据 Nonce + Key 格式化:构造 B_0 块(标志+Nonce+长度) CBC-MAC 认证计算 B_0 → B_1 → ... → B_n(串行计算,每个块依赖前一个) CTR 模式加密 计数器递增,逐块加密明文,同时加密MAC生成认证标签 输出:密文 + 认证标签(MIC) 先认证 后加密 CTR模式 可并行

流程其实不复杂:

  1. 格式化:把Nonce、标志位和长度信息拼成B_0块。这是CBC-MAC的第一个输入块。
  2. CBC-MAC认证:从B_0开始,依次处理所有认证数据和明文数据。每个块的输出是下一个块的输入——这就是串行依赖的来源。
  3. CTR加密:用计数器模式加密明文,同时把上一步算出的MAC也加密一遍,生成最终的认证标签。
  4. 输出:密文 + 加密后的MAC(也叫MIC,Message Integrity Code)。

我的经验:在实现CCM时,很多人会忽略B_0块的构造细节。B_0里的标志位决定了Nonce长度和MAC长度,一旦算错,整个认证过程就全废了。我建议你在写代码时,先把B_0的每个bit都画出来,对着协议规范逐位核对。

1.5 为什么内存占用是核心问题?

好,现在咱们回到课程的主题——内存占用。CCM的内存问题主要出在认证阶段:

  • CBC-MAC需要完整输入:认证计算时,你得把整个明文(或者至少一个完整的16字节块)准备好。如果明文很大,缓冲区就得很大。
  • 先认证后加密导致双重缓冲:认证阶段需要明文,加密阶段也需要明文。如果你不能原地操作,就得准备两份缓冲区。
  • Nonce和附加数据也要存:这些数据虽然不大,但在资源受限的系统里,每一字节都得计较。

相比之下,GCM的GHASH虽然计算复杂,但它的内存模型更友好——可以边收数据边认证,不需要额外的大缓冲区。这就是为什么很多高性能场景选GCM,而资源受限场景(比如物联网)还在用CCM。

嗯,这一章咱们把CCM的底子打好了。后面的章节会深入每个模块的内存布局,包括CBC-MAC的中间状态、CTR计数器的管理、以及如何在只有几KB RAM的MCU上跑通CCM。到时候我会拿实际项目的代码来拆解,咱们一步步把内存占用算清楚。