AP AUTOSAR硬核技术(6):加密模块FC Crypto

汽车   2024-11-18 08:03   上海  
全文约8800字,建议收藏阅读

作者:刘向
出品:汽车电子与软件

         

 

在现代汽车电子系统中,安全性已经成为关键问题之一,尤其是在自动驾驶、车联网以及智能汽车服务中。为了确保数据的保密性、完整性和认证,Adaptive AUTOSAR 引入了加密模块Cryptography,即 Crypto 功能集群(FC Crypto),为应用程序和功能集群提供加密服务。FC Crypto 模块通过一个标准化的接口(CryptoAPI),使得应用程序能够实现加密操作、密钥管理和证书处理等关键安全功能。
         

 

本文将深入探讨加密技术在AUTOSAR系统中的实现,重点介绍与密钥管理相关的操作及其实现方式。



#01
FC Crypto概述 

在 AUTOSAR 系统中,Cryptography Functional Cluster 提供了对各种加密操作的支持。该组件不仅包含了常见的加密算法(如 AES、RSA 等),还实现了密钥管理、数字签名、身份验证等核心功能。

1.1 架构  


如图1.1所示,加密学的高层架构将功能划分为三个主要部分:密钥存储、加密算法提供者管理和证书管理。每个部分功能独立,它们之间的交互通过自适应平台 API 进行协调。  
 

图1.1  High-level CryptoAPI architecture

Crypto模块为自适应AUTOSAR的应用程序和功能集群提供安全加密服务,包括:

  • 加密操作:提供公钥加密、对称密钥加密等操作,用于保护敏感数据的机密性。

  • 密钥管理:提供密钥存储、密钥生成、密钥分发等功能,确保加密密钥的安全管理。

  • 证书管理:支持X.509证书的管理,包括证书的颁发、吊销、验证和存储。

Crypto功能集群通过CryptoAPI提供了一个统一接口,使应用程序能够轻松地执行加密、解密、身份验证和完整性检查等操作,从而保障汽车服务的安全性。特别是在需要高性能或硬件加速时, Crypto FC还可以支持第三方加密库或硬件模块的集成。

1.1.1 密钥存储  


密钥存储负责密钥管理,包括密钥的安全存储(例如,加密密钥),因此得名。密钥存储在密钥槽中,每个槽位可以存储一个密钥。在自适应应用程序使用密钥之前,必须知道密钥存储在哪个密钥槽中。这是通过一个实例描述符(InstanceSpecifier)实现的,具体操作流程如下: 
 
首先自适应应用程序开发者首先创建一个类型为 CryptoKeySlotInterface 的RPortPrototype,然后使用这个端口原型的实例描述符InstanceSpecifier来访问密钥槽。因此,自适应应用程序永远不会直接引用特定的密钥槽,而是引用 RPortPrototype。因此,在映射过程中,集成者可以自由选择任何密钥槽。密钥存储的概览如图1.2 所示。


图1.2 Key slot to Adaptive Application mapping

出于安全原因,自适应应用程序无法直接访问原始密钥数据,而是使用密钥句柄(Key Handles)。这种设计确保了在应用程序被攻击时,密钥仍然安全(换句话说,自适应应用程序无法泄漏密钥)。完整性是加密学中的一个重要概念,因此密钥必须防止在内存中被篡改。密钥管理的实现方式确保了在断电等情况下密钥槽不会受到破坏。

1.1.2 加密提供者管理  


加密提供者(Crypto Provider)负责加密原语的实现。它支持多种加密算法,如对称密钥加密(AES)、公钥加密(RSA、ECC)、哈希算法(SHA-256)、数字签名等。加密提供者可以是基于软件的加密库,也可以是硬件加速模块(如TPM、HSM)。每个加密提供者实例代表一组加密算法的实现,通常隔离在独立的处理空间,确保密钥和加密数据的安全性。加密提供者使自适应应用程序能够以可移植的方式在不同的加密提供者之间切换。使用加密提供者可以使自适应应用程序执行基本的加密操作(如加密和解密)。  
 
如图 1.3 所示,开发者首先创建一个类型为 CryptoProviderInterface 的,然后使用该端口的实例描述符来获取访问已实现功能的权限。

在部署过程中,集成者将这个端口映射到一个可用的加密提供者。


图1.3 Crypto provider to Adaptive Application mapping

从自适应应用程序的角度来看,只要加密提供者实现了相同的功能,提供者是可以互换的,选择合适的实现是集成者的责任。并且并非每个加密提供者(即加密库)都会实现相同的算法集。另一方面,当需要进行时间敏感的计算时,硬件加速可能是必需的。因此,自适应平台支持每个自适应应用程序使用多个加密提供者,尽管不同提供者支持的实现可能会有所不同。

1.1.3 X.509证书管理  


X.509 证书是一种基于国际电信联盟 (ITU) X.509 标准的数字文档,用于在互联网和计算机网络中验证身份和保护数据。它本质上是一个公钥证书,包含证书持有者的公钥、相关信息以及证书颁发机构 (CA) 的数字签名。   


图1.4 Certificate Management

自适应平台支持符合国际电信联盟(ITU)[12] X.509标准的公钥证书管理。证书将关于密钥的信息(即它所认证的公钥)与该密钥拥有者的信息绑定在一起。这使得证书的使用者能够验证私钥的所有者身份,检查私钥与公钥是否匹配等。

自适应平台提供了以下 X.509 证书功能:

  • 完整解析 X.509 证书。

  • 基于不同参数在本地存储中搜索证书。

  • 将证书存储在易失性或持久存储中,并进行加载。

  • 将证书导入到易失性或持久存储中。

  • 存储证书。

1.2 配置与工作流(本文以ETAS的RTA-VRTE工具为例)  


Crypto的工作流分为两个阶段:配置和应用设计。对于密钥和加密提供者的工作流非常相似,主要区别在于元素和属性的名称和类型。 
 

图1.5 Cryptography Workflow

1.2.1 配置阶段  


加密模块的配置通过其实例化来提供。需要在包含加密守护进程的机器元素下创建一个包含加密提供者对象的Crypto Module Instantiation。以下是加密模块实例化元素的创建过程。
         

 

1) 加密提供者

要创建加密提供者,通常在Crypto Module Instantiation,创建Crypto Provider。

加密提供者提供一个接口,用于访问实现加密算法的库。出于安全考虑,加密原语的实现是外部于自适应平台的。RTA-VRTE 默认配备了WolfSSL®加密算法库,且该库可以通过将WOLFCRYPT_PROVIDER作为提供者来自动配置。
         

 

2) 多加密提供者

在 RTA-VRTE 中预置了WolfSSL 加密提供者。然而,可以在同一机器上使用额外的Crypto Module Instantiation实例化多个WolfSSL 加密提供者。
         

 

3) 密钥槽Key Slot   

每个加密提供者可以配置为管理不同的密钥。每个密钥可以作为加密提供者的子项进行创建。密钥及其属性会存储在特定的文件夹中。如果守护进程稍后填充密钥,则此文件夹中的文件可能最初为空。

  • 部署类型(deploymentType:):标识符identifiers描述加密提供者如何处理密钥。每个加密提供者可能会开发成管理不同类型的密钥(例如软件密钥、硬件密钥等)。例如WOLFCRYPT_PROVIDER允许的类型SWKeyFile。

  • 密钥槽编号(keySlotNum:):密钥的唯一标识符。该槽编号应在机器上对所有密钥唯一。自适应应用程序不需要使用该编号,它们应使用密钥实例描述符(key instance specifier)

4) 证书(Certificate )

与密钥槽类似,证书也需要配置。它们可以在Crypto Module Instantiation*下创建,按照 AUTOSAR 标准清单的要求添加:

  1. 部署细节:这是存储证书及其属性的二进制文件的路径。如果传递了 `-c` 参数,则为相对路径。


  2. 部署类型:标识符描述加密提供者如何处理证书。每个加密提供者可能会开发成管理不同类型的证书(例如软件证书、硬件证书等)。例如WOLFCRYPT_PROVIDER,允许的证书类型是 SWCertFile。


  3. 关联提供者:管理证书的加密提供者,例如 WOLFCRYPT_PROVIDER。
         

 

1.2.2 应用设计阶段  


应用程序的设计需要经过适当的规划。完成设计后,自适应应用程序可以被集成并配置。

a. 应用程序原型    

应用程序需要通过端口与提供者和密钥槽连接。可以在 .hadl文件中创建这些端口:

interfaceappProviderInterface
interfaceappKeySlotInterface
interfaceappCertificateInterface
component AraCRYPTO_Example_prototype {
  require portProvider for appProviderInterface
  require portKeySlot for appKeySlotInterface
  require portCert for appCertificateInterface
}

然后,将此原型关联到适当的可执行文件中,使用Execution Editor进行配置。以下是如何使用的代码示例:

auto instanceSpec = ara::core::InstanceSpecifier(           "AraCRYPTO_Example/AraCRYPTO_Example_prototype/portKeySlot");
auto storageProvider = ara::crypto::LoadKeyStorageProvider();
auto keySlot = storageProvider->LoadKeySlot(instanceSpec).Value();
         

 

const ara::core::InstanceSpecifier wolfsslInstanceSpecifier(
   "AraCRYPTO_Example/AraCRYPTO_Example_prototype/portProvider");
ara::crypto::cryp::CryptoProvider::Uptr cryptoProvider =
    ara::crypto::LoadCryptoProvider(wolfsslInstanceSpecifier);
         

 

auto appCertificate = ara::core::InstanceSpecifier(
   "AraCRYPTO_Example/AraCRYPTO_Example_prototype/portCert");
auto X509Provider = LoadX509Provider();
auto resPreloadedCert = X509Provider->LoadCertificate(appCertificate);

注意:标识实例描述符InstanceSpecifier的字符串是由可执行文件/原型/端口组成的。
         

 

b. 应用集成

为了实现正确的集成,应用程序的端口应映射到配置中的密钥和提供者。这可以通过在清单中使用适当的元素来完成。
         

 

首先,创建映射元素“Crypto Key Slot To Port Prototype Mapping”,将元素类型写为CryptoKeySlotToPortPrototypeMapping,并适当填写其他参数。然后,填写Key Slot和Process属性。通过这个元素配置中的密钥和整个进程被关联起来。接下来,应选择适当的端口,使用端口原型实例引用。此过程可以对加密提供者和证书进行重复。对应的映射属性名称分别为CryptoProviderToPortPrototypeMapping和 CryptoCertificateToPortPrototypeMapping。   

1.3 安全目标与实现  


FC Crypto通过加密和密钥管理服务帮助实现以下安全目标:

  • 认证:通过加密和数字签名机制,FC Crypto确保应用程序能够证明其身份,防止未授权访问。

  • 不可抵赖性:使用数字签名和哈希算法确保行为的不可否认性,即操作后不能否认已执行的动作。

  • 机密性:通过加密保护敏感数据(如用户数据、车辆控制信息),确保数据只有持有正确密钥的接收者能够读取。

  • 完整性:通过哈希和签名机制,确保数据在存储或传输过程中不被篡改,接收方可以验证数据的完整性。
         

 

1.4 密钥与证书管理  


FC Crypto的密钥管理与证书管理是整个系统安全的关键部分。以下是其主要功能:

1.4.1 密钥管理概念  


密钥管理在加密过程中至关重要,它涉及到如何生成、存储、分发以及销毁密钥。在 AUTOSAR 中,密钥管理方案通常包括以下几个方面:
   
  • 密钥生成:系统会动态生成对称密钥和非对称密钥对(如 RSA 公私钥对、ED25519 签名密钥对等)。

  • 密钥存储与持久化:FC Crypto通过密钥存储提供者管理密钥的持久化存储。密钥材料可以存储在硬件模块(如TPM、HSM)或软件存储中,系统通过统一的接口进行访问。

加密密钥通常也可以被存储在硬件安全模块(HSM)或加密服务中,以确保密钥不被泄露或丢失。

  • 密钥注入:密钥可以在系统运行时注入到加密模块中,或在部署过程中预先加载。

  • 密钥用途限制:密钥存储提供者确保只有授权的应用程序可以访问密钥,避免不当访问导致安全问题。

独占访问模式:Crypto FC提供密钥槽的独占访问模型,确保只有指定的应用程序可以访问特定的密钥槽。
         

 

1.4.2 关键字段与结构体  


在 AUTOSAR 中,密钥管理结构体中包含了一些重要字段,以下是一些核心字段的解释:

  • metadataVersionNumber:表示当前数据编码所依据的模式版本。如:版本支持的编码版本号为 0 和 1,其中 1 是针对导出操作,0 是针对导入操作。

  • COUID(Cryptographic Object Unique Identifier):由mObjectUidQwordMs、mObjectUidQwordLs 和 mObjectUidVersionStamp 组成,即这三者共同组成了一个加密对象唯一标识符(COUID)。  


    在 AUTOSAR 中,COUID 被分为两部分:GeneratorUid由mObjectUidQwordMs.mObjectUidQwordLs 和 mObjectUidVersionStamp 组成,用来唯一标识由生成器生成(注入)的对象(密钥)。


    AUTOSAR 要求每个密钥都有自己的 COUID,且在执行 ASN.1 编码时必须提供对于注入密钥的 COUID。


    注意:在 AUTOSAR 中,空的 COUID(Nil)等于 0.0.0,因此建议从 0.1.1 开始编号。当注入非对称密钥时,预计公钥和私钥将共享相同的 COUID,但它们会因 mObjectType 的不同而有所区分。

  • mObjectSize:表示密钥对象的字节大小。例如,对于一个 2048 位的 RSA 密钥,mObjectSize 应该设置为 256 字节。

  • mObjectType:表示正在注入的密钥类型。可用的类型包括:

  • kSymmetricKey:对称密钥

  • kPrivateKey:私钥

  • kPublicKey:公钥

  • kSignature:签名

  • kSecretSeed:秘密种子
         

 

  • mAlgId:指定密钥使用的加密算法 ID,如 AES-128、RSA-2048、ED25519 等。

  • mContentAllowedUsage:指定密钥支持的加密操作,可以支持多种加密、解密、签名、验证等操作。(可以通过按位 OR 运算符进行组合)

  • kAllowPrototypedOnly = 0x0000

  • kAllowDataEncryption = 0x0001

  • kAllowDataDecryption = 0x0002    

  • kAllowSignature = 0x0004

  • kAllowVerification = 0x0008

  • kAllowKeyAgreement = 0x0010

  • kAllowKeyDiversify = 0x0020等等

注意:<< 操作符表示左位移运算。

如果密钥的 mContentAllowedUsage 设置为 kAllowPrototypedOnly,则该密钥不能用于任何加密操作。这种设置通常用于预留密钥插槽,以便后续注入密钥。
         

 

  • mObjectEncoding:表示密钥编码格式,包括 raw、der、pem 和 custom(自定义,特指某个目标加密提供程序的特殊编码)格式。

  • mSessionFlag:指示密钥是否为临时密钥。如果设置为 true,表示该密钥仅用于当前会话,不会持久化存储。

  • mObject:密钥的值,按照 mObjectEncoding 中指定的方式进行编码。
         

 

1.4.3 密钥生成  


密钥生成过程可以分为以下几个步骤:

1. 选择加密提供者

首先,选择一个加密提供者。此提供者用于执行所有加密操作。以下代码展示了如何加载加密提供者:

const InstanceSpecifier cryptoProv("AraCRYPTO_Example/AraCRYPTO_Example_prototype/portProvider");
cryp::CryptoProvider::Uptr cp = LoadCryptoProvider(cryptoProv);

2. 获取算法 ID

第二步是获取加密算法的 ID。每个算法都有一个唯一的 ID,可以通过算法名称来获取。例如,下面是获取 RSA-2048 算法 ID 的代码:
         

 

   
auto keyAlgId = cryptoProvider->ConvertToAlgId("RSA-2048");

3. 生成私钥

使用上述算法 ID 可以生成私钥,并且可以指定密钥的使用权限(mContentAllowedUsage)。例如,生成一个可以用于数据加密和解密的私钥:

auto privateKeyRes = cryptoProvider->GeneratePrivateKey(keyAlgId, kAllowDataEncryption | kAllowDataDecryption);
auto& privateKey = privateKeyRes.Value();

4. 生成公钥

在生成私钥之后,可以通过私钥获取相应的公钥:

auto publicKeyRes = privateKey->GetPublicKey();
auto& publicKey = publicKeyRes.Value();
         

 

1.4.4 密钥注入 


密钥注入涉及将加密密钥导入到密钥存储槽中,具体过程包括以下几个步骤:
         

 

1. 加载密钥槽

加载密钥槽的步骤可以分为三个操作:

  • 创建 InstanceSpecifier,指定密钥槽的位置。

  • 加载密钥存储提供者。

  • 加载密钥槽并获取对该槽的访问权限。

auto instanceSpec = InstanceSpecifier("AraCRYPTO_Example/AraCRYPTO_Example_prototype/portKeySlot");
auto sp = LoadKeyStorageProvider();
auto keySlot = sp->LoadKeySlot(instanceSpec).Value();
         

 

2. 选择加密提供者

类似地,选择并加载加密提供者。

const InstanceSpecifier cryptoProv("AraCRYPTO_Example/AraCRYPTO_Example_prototype/portProvider");    
cryp::CryptoProvider::Uptr cp=LoadCryptoProvider(cryptoProv);
         

 

3. 打开密钥槽

打开密钥槽以进行写访问,以便可以向其中注入密钥。

auto ioi = keySlot->Open(false, true).Value();
         

 

4. 准备加密对象

第四步是准备一个对象,以便将其注入或导入到密钥槽中。在将编码密钥引入自适应应用程序时,有多种选择。例如,编码密钥可以通过网络从密钥服务器检索,或直接从机器上的文件读取。为了清晰起见,此示例在自适应应用程序的源代码中硬编码了编码密钥。

// 编码密钥存储为字节向量
const std::vector<:uint8_t>input {……};<:uint8_t>
<:uint8_t>
一旦应用程序获得了编码密钥,它可以被包装在一个 ReadOnlyMemRegion 中,并在接下来的步骤中注入到密钥槽中。
         

 

ReadOnlyMemRegion inData{input.data(), input.size()};
         

 

5. 密钥注入

最后,密钥可以被注入到槽中。注入是通过 ImportPublicObject 方法进行的,该方法将密钥句柄作为第一个参数,编码密钥作为第二个参数,ara::crypto::CryptoObjectType::kUndefined 作为第三个参数。

auto importResult = cp->ImportPublicObject(*ioi, inData, CryptoObjectType::kUndefined);

成功调用 ImportPublicObject 方法后,密钥注入完成,密钥可以使用。
         

 

   
注意:最安全的注入密钥方式是使用 ImportSecuredObject。但此 API 要求机器上已经存在另一个密钥,并且该密钥应离线用于编码要注入的密钥。

1.4.5 密钥包装与安全注入  


在某些情况下,密钥可能会使用 密钥包装 技术进行加密,以提高安全性。密钥包装是指将密钥使用另一个密钥(称为 KEK,密钥加密密钥)加密。然后,通过密钥解包过程恢复原始密钥。此过程涉及以下步骤:

  1. 打开密钥槽:加载已存储的 KEK 和待注入密钥的密钥槽。


  2. 创建密钥包装上下文:使用加密算法创建包装上下文。


  3. 准备待注入的密钥:将待注入的密钥数据准备好。


  4. 密钥注入:通过 ImportSecuredObject 接口将解密后的密钥注入到目标密钥槽。
         

 

包装密钥注入

出于安全原因,密钥可能会通过一种称为密钥包装的过程进行编码。在将密钥注入密钥槽之前,必须先解包。解包过程要求密钥加密密钥(KEK)已预加载到目标设备上。然后使用 CreateSymmetricKeyWrapperCtx 函数利用此 KEK。此过程的主要步骤如下:

1.密钥槽开启 需要两个密钥槽:一个用于已部署的密钥,另一个用于要注入的密钥。这些槽必须打开。有关密钥槽开启的详细说明,请参阅上一节。

2.创建和配置包装上下文 解密密钥需要特定的上下文,可以使用 CryptoProviderCreateSymmetricKeyWrapperCtx 创建。

auto algId = cryptoProvider->ConvertToAlgId("KEYWRAP/AES-128");
auto keyWrapperCtx = cryptoProvider->CreateSymmetricKeyWrapperCtx(algId).Value();
auto kek = cryptoProvider->LoadSymmetricKey(*(ioiDeployed));
auto voidRes = keyWrapperCtx->SetKey(*kek.Value(), CryptoTransform::kDecrypt);    
         

 

3.准备注入密钥 ImportSecuredObject API 需要一个 ReadOnlyMemRegion 作为输入参数。因此,注入的密钥需要以这种格式存在。例如,可以将导入的向量转换如下:

const std::vector<:uint8_t>inputKey { ... ...};
ReadOnlyMemRegion keyData{inputKey.data(), inputKey.size()};
         

 

4.密钥注入 准备工作完成后,现在可以导入密钥:

cryptoProvider->ImportSecuredObject(*ioiInjected, keyData, *keyWrapperCtx);

1.5 对称加密与解密  


对称加密是一种使用相同密钥进行加密和解密的加密方法。它的主要特点是速度快,适用于大量数据的加密。常见的对称加密算法包括AES(高级加密标准)、DES(数据加密标准)等。

对称加密过程:

  • 密钥生成:生成一个对称密钥。

  • 数据加密:使用对称密钥对明文数据进行加密,生成密文。

  • 数据传输:将密文传输给接收方。

  • 数据解密:接收方使用相同的对称密钥对密文进行解密,恢复出明文。

对称解密过程与加密过程相反,使用相同的密钥将密文转换回明文。

1.5.1 对称加密  


对称加密代码可以分为六个独立的部分:

1. 加载密钥槽

加载密钥槽包括三个简单的操作。简而言之InstanceSpecifier标识存储密钥的密钥槽,LoadKeySlot`方法创建一个密钥槽句柄,以便访问密钥。   

   auto instanceSpec = InstanceSpecifier("AraCRYPTO_Example/AraCRYPTO_Example_prototype/portKeySlot");
   auto sp = LoadKeyStorageProvider();
   auto keySlot = sp->LoadKeySlot(instanceSpec).Value();
         

 

2. 选择加密提供者

第二步,我们加载用于执行加密的加密提供者。

   const InstanceSpecifier cryptoProv("AraCRYPTO_Example/AraCRYPTO_Example_prototype/portProvider");
   cryp::CryptoProvider::Uptr cp = LoadCryptoProvider(cryptoProv);
         

 

3. 加载密钥

现在需要使用Open方法打开加载的密钥槽(如果不打开密钥槽,我们无法访问存储在其中的密钥)。打开后,加密提供者可以从该密钥槽加载密钥,以便在加密操作中直接访问密钥。

   auto resultIoi = keySlot->Open(false, false);
   auto aesKey = cp->LoadSymmetricKey(*(resultIoi.Value()));
         

 

4. 准备用于加密的对象

在本节中,我们准备用于加密的数据。我们使用来自 OpenSSL 的测试向量。

// 数据存储为字节向量

   const std::vector<:uint8_t>initializationVector{…};
   const std::vector<:uint8_t>plainText{…};
   const std::vector<:uint8_t>cipherText{…};<:uint8_t>
<:uint8_t>
在使用 `ara::crypto` API 之前,我们需要将数据包装在 `ReadOnlyMemRegion` 中。对于加密,我们需要包装初始化向量和明文(即未加密的数据)。
      ReadOnlyMemRegion initializationVectorWrap{initializationVector.data(), initializationVector.size()};    
   ReadOnlyMemRegion plainTextWrap{plainText.data(), plainText.size()};
         

 

5. 准备加密上下文

首先,找到所使用的加密算法的供应商特定 ID。这是通过 ConvertToAlgId方法完成的。输入参数是一个符合 AUTOSAR 加密原语命名约定的字符串。

   std::uint64_t algoID = cp->ConvertToAlgId("CBC/AES-128/PKCS7");
   cryp::StreamCipherCtx::Uptr encryptionCtx = cp->CreateStreamCipherCtx(algoID).Value();

最后,指定要执行的操作类型和密钥,在本例中为加密。

   encryptionCtx->SetKey(*aesKey.Value(), CryptoTransform::kEncrypt);
         

 

6. 执行加密

对于每个基于流密码的加密计算,调用 Start和 FinishBytes方法。算法需要一个初始化向量,因此将其作为参数传递给 Start方法。FinishBytes方法接收未加密的数据作为输入,并为其添加填充。填充的目的是将输入数据的大小增加到块大小的倍数(如果输入数据的大小已经是块大小的倍数,则添加一个完整的填充块)。然后返回加密数据。在此调用之后,加密过程完成。

   encryptionCtx->Start(initializationVectorWrap);
   auto encryptedText = encryptionCtx->FinishBytes(plainTextWrap).Value();

在开发阶段,检查刚刚计算的加密文本是否与预期值匹配可能很有用。这一步告诉我们加密功能集群是否正确部署。为了增加信心,可以使用商业级开源工具包生成测试向量。
         

 

   

1.5.2 对称解密  


解密与加密非常相似,仅在几个关键点上有所不同。加载密钥槽、选择加密提供者和加载密钥的步骤相同(为了简洁,这些步骤的描述在此不再重复)。然而,有三个主要区别:
         

 

1. 准备用于解密的对象

使用相同的 OpenSSL 测试向量,因此数据保持不变。将加密数据包装在 ReadOnlyMemRegion中。

   ReadOnlyMemRegion initializationVectorWrap{initializationVector.data(), initializationVector.size()};
   ReadOnlyMemRegion cipherTextWrap{cipherText.data(), cipherText.size()};
         

 

2. 准备解密上下文

查找供应商特定算法 ID 的代码和创建加密上下文的代码没有变化。当设置密钥时,还需要指定执行的操作类型(在本例中为解密,`kDecrypt`)。

   std::uint64_t algoID = cp->ConvertToAlgId("CBC/AES-128/PKCS7");
   cryp::StreamCipherCtx::Uptr decryptionCtx = cp->CreateStreamCipherCtx(algoID).Value();
   decryptionCtx->SetKey(*aesKey.Value(), CryptoTransform::kDecrypt);
         

 

3. 执行解密

对于解密,也需要调用 `Start` 和 `FinishBytes` 方法。对 `Start` 方法的调用保持不变——在这里传递初始化向量。然而,当我们调用 `FinishBytes` 方法时,加密数据作为参数传递。

   decryptionCtx->Start(initializationVectorWrap);
   auto decryptedText = decryptionCtx->FinishBytes(cipherTextWrap).Value();    

可以看出,用于解密的代码与用于加密的代码非常相似。为了检查加密功能集群是否已正确部署,将计算值与测试向量中的数据进行交叉检查:

for (auto i = 0; i < decryptedText.size(); ++i)
{
    if (static_cast<:uint8_t>(decryptedText[i]) != plainText[i])
    {
        return 1;
    }
}
return 0;
         

 

1.6 非对称加密和解密  


非对称加密使用一对密钥:公钥和私钥。公钥用于加密,私钥用于解密。非对称加密的主要特点是安全性高,适用于密钥交换和数字签名等场景。常见的非对称加密算法包括RSA、ECC(椭圆曲线加密)等。

非对称加密过程:

  • 密钥对生成:生成一对密钥,包括公钥和私钥。

  • 数据加密:发送方使用接收方的公钥对明文数据进行加密,生成密文。

  • 数据传输:将密文传输给接收方。

  • 数据解密:接收方使用自己的私钥对密文进行解密,恢复出明文。

非对称解密过程与加密过程相反,使用私钥将密文转换回明文。   
         

 

非对称算法(例如 RSA)的加密和解密过程有所不同。以下步骤展示了如何使用 RSA-2048 密钥加密/解密消息。在此之前,需要生成密钥。该过程与对称算法类似,但有一些区别。例如,创建加密上下文的 API 与解密 API 不同:CreateEncryptorPublicCtx 和CreateDecryptorPrivateCtx。

1.6.1 非对称加密  


1. 创建加密上下文

   // RSA 加密算法的实现 ID

   const auto encAlgId = cryptoProvider->ConvertToAlgId("ENC/RSA-2048/PKCS1-v1_5");
   
  // 创建加密上下文

   auto encCtx = cryptoProvider->CreateEncryptorPublicCtx(encAlgId).Value();
         

 

2. 设置公钥

   // 为加密上下文设置公钥

   encCtx->SetKey(*publicKey);
         

 

3. 加密消息

   const std::vector<:uint8_t>expectedMsg{0x42, 0x23, 0x05, 0x50, 0x42};
   auto cipher = encCtx->ProcessBlock(expectedMsg);
         

 

   

1.6.2 非对称解密  


加密后,解密消息的步骤:

1. 创建解密上下文

  // RSA 解密算法的实现 ID

   const auto decAlgId = cryptoProvider->ConvertToAlgId("DEC/RSA-2048/PKCS1-v1_5");

   // 创建解密上下文

   cryp::DecryptorPrivateCtx::Uptr decCtx = cryptoProvider->CreateDecryptorPrivateCtx(decAlgId).Value();
         

 

2. 设置私钥

   // 为解密设置私钥

   decCtx->SetKey(*privateKey);
         

 

3. 解密消息

   ara::crypto::ReadOnlyMemRegion inDec(reinterpret_cast(cipher->data()), cipher->size());
   auto msg = decCtx->ProcessBlock(inDec);

4. 结果验证

最后,可以通过将解密后的消息与原始消息进行比较来验证结果:

   for (auto i = 0; i < expectedMsg.size(); ++i)
   {
       if (static_cast<:uint8_t>(msg.Value()[i]) != expectedMsg[i])
       {
           return 1;    
       }
   }
         

 

1.6.3 对称加密与非对称加密对比  


  • 速度:对称加密速度快,适合大数据量加密;非对称加密速度较慢,适合小数据量加密和密钥交换。

  • 安全性:非对称加密安全性更高,因为公钥可以公开,而私钥保密。

  • 应用场景:对称加密常用于数据传输的加密;非对称加密常用于密钥交换、数字签名和身份验证。
         

 

1.7 数字签名  


数字签名是一种数字消息的认证方案,通常基于非对称加密。以下步骤展示了如何签署消息以及如何验证其真实性。本示例使用 ED25519方案。在此之前,需要生成密钥。

         

 

1. 签名

   //创建签名上下文:

   auto sigAlgId = cryptoProvider->ConvertToAlgId("EDDSA/ED25519");
   auto signer = cryptoProvider->CreateSignerPrivateCtx(sigAlgId).Value();

   //设置私钥:

   signer->SetKey((*privateKey));    

   //签署消息:

   ara::core::Vector<:uint8_t>messageToSign{0xaf, 0x82};
   auto signatureRes = signer->Sign(messageToSign);

2. 验证

   签名后,可以验证消息及其签名。第一步是创建验证上下文:

   // ED25519 验证算法的实现 ID

   auto sigAlgId = cryptoProvider->ConvertToAlgId("EDDSA/ED25519");

   // 创建验证上下文

   auto verifier = cryptoProvider->CreateVerifierPublicCtx(sigAlgId).Value();

   //设置公钥:

   verifier->SetKey((*publicKey));

   //准备验证数据:

   auto& signature = signatureRes.Value();
   ReadOnlyMemRegion signatureMem{reinterpret_cast(signatureRes->data()), signatureRes->size()};
   ReadOnlyMemRegion valueMem(messageToSign.data(), messageToSign.size());

   //验证消息:

   auto verifyRes = verifier->Verify(messageToSign, signatureMem);
     


#02
总  结 
 
FC Crypto是Adaptive AUTOSAR中不可或缺的模块,为应用程序提供全面的加密服务,包括数据加密、密钥管理、证书管理等。通过CryptoAPI,FC Crypto为不同的应用场景提供了标准化接口,确保了系统的机密性、完整性、认证性和不可否认性。FC Crypto进一步增强了汽车电子系统的安全性,满足了现代汽车对数据保护和安全通信的高要求。


本文作者:刘向,汽车嵌入式工程师



-end-

本专栏是由汽车电子与软件打造的中立性技术科普专栏,将系统地阐述软件定义汽车下的关键挑战和工程实践。欢迎订阅本专栏!

汽车电子与软件
每天分享一篇技术文章!
 最新文章