[译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密

2023-10-29

原文地址:Security Best Practices: Symmetric Encryption with AES in Java and Android

最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密

我将在本文中为大家介绍高级加密标准(AES),常见块模式,为什么需要填充和初始化向量以及如何保护数据不被篡改。最后,我将为大家展示如何使用 Java 轻松实现此功能,从而避免大多数安全问题。

为什么每一个软件工程师都需要知道 AES

AES,又称 Rijndael 加密算法,在 2000 年被 NIST 选中以用来替换过时的数据加密标准(DES)。AES 是一种分组密码,这意味着加密发生在固定长度的比特组上。在我们的例子中,算法定义块长度为 128 位。AES 支持 128,192 和 256 位的密钥长度。

每个块都经历多轮转换。我将在这里省略算法的细节,对算法感兴趣的读者可以参考维基百科中有关 AES 的文章。这里需要指出的是块大小受转换轮次的重复次数影响(128 位密钥是 10 个周期,256 位为 14 个周期),而密钥长度并不影响它的大小。

一直到 2009 年 5 月,唯一一次成功发布,针对完整 AES 的攻击是对某些特定实现的旁道攻击。(资源

想要加密多个块?

AES 只会加密 128 位数据,如果我们想要加密整个消息,我们需要选择一种块模式,利用该模式可以将多个块加密为一个密文。最简单的块模式是电子密码本或 ECB。它将在每个区块中使用相同的未更改的键:

图片来自维基百科

这将是特别糟糕的,因为相同的明文会被加密成相同的密文。

使用 ECB 块模式加密的图片显示原始图案(自己尝试一下

请记住,除非你只加密小于 128 位的数据,否则永远不要选择该模式。不幸的是,它仍然被经常误用,因为它不需要你提供初始向量(稍后会详细介绍),因此开发人员似乎更容易处理。

必须使用块模式处理的一种情况:如果最后一个块的大小不足 128 位会发生什么?这就是填充发挥作用的地方,即填充块的缺失位。最简单的方式是用零填充缺失位。在 AES 中选择填充几乎没有任何安全隐患

密码分组链接(CBC)

那么有什么方案可以替代 ECB 呢?例如 CBC,在该模式中,用当前的明文块和前一个密文块进行异或在该方法中,每个密文块都依赖于它前面的所有明文块。使用与之前相同的图片,加密结果将是与噪声数据无法区分的随机数据:

使用 CBC 块模式加密的图片看起来是随机的

那如何处理第一个块呢?最简单的方法是使用一个完整的填充块(比如用零填充),但这样每次加密相同密钥和明文都会产生一样的密文。此外,如果你为不同的明文重用相同的密钥,那么恢复密钥将会更加容易。更好的方法是使用随机初始化向量(IV)。这对于随机数据来说只是一个奇特的词,大约是一个块(128 位)大小。将它想象成一个加密的 salt,也就是说,IV 是可以公开的,随机的且只能使用一次。但请注意,因为 CBC 将密文异或而不是前一个明文的明文,因此 IV 不仅仅会阻止第一个块的解密。

在传输或保持数据时,通常只将 IV 添加到实际的密码消息中。如果你对如何正确使用 AES-CBC 感兴趣,请阅读本系列的第 2 部分

记数模式(CTR)

另外一种选择是使用 CTR 模式。这种模式很有意思,因为它会将密码转换为密码流,这意味着不需要进行填充。在其基本形式中,所有块的编号为 0 到 n。现在每个块都将使用密钥、IV(此处也称为 nonce)和计数器的值来进行加密。

图片来自维基百科

与 CBC 不同,它的优点是可以进行并行加密并且所有块都依赖于 IV,而不仅仅是第一个。一个很严重的警告是,IV 永远不能被相同的密钥重用,因为攻击者可以从中轻松计算出你所使用的密钥。

我可以确保没有人能够修改我的消息吗?

事实:加密不会自动防止数据修改。这实际上是一种非常常见的攻击。有关该问题更全面的讨论,请阅读此文

那么我们又能做些什么呢?我们只需将加密验证码(MAC)添加到加密邮件中。MAC 类似于数字签名,不同之处在于验证和验证密钥实际上是相同的。这种方法有不同的变化大多数研究人员推荐的模式叫做 Encrypt-then-Mac 。也就是说,在加密之后,在密文上计算并附加 MAC。你通常会使用基于哈希的消息身份验证代码(HMAC)作为 MAC 的类型。

现在它开始变得复杂了。为了完整性/真实性我们必须选择 MAC 算法,选择加密标签模式,计算 mac 并附加它。因为整个消息必须处理两次,所以该操作运行速度缓慢。反向操作必须与前面一致,但用于解密和验证。

使用 GCM 进行认证加密

如果有模式可以处理所有的身份验证,那不是很好吗?幸运的是有一种称为认证加密的加密方式,它同时为数据的机密性、完整性和真实性提供了保证。支持此功能最流行的块模式之一为 Galois/Counter Mode or GCM(比如它可以使用 TLS v1.2 中的密码组件)。

GCM 基于 CTR 模式,它还在加密期间顺序计算身份验证标记。然后该标记通常会附加到密文中。它的大小是一个重要的安全属性,因此它的长度至少是 128 位。

它还可以验证未包括在明文中的附加信息。该数据称为关联数据。这为什么有用呢?例如,加密数据具有元属性,即用于检查是否必须重新加载内容的创建日期。攻击者可以轻松更改创建日期,但如果将其添加为关联数据, CGM 将验证此信息并识别出更改。

激烈的讨论:使用多长的密钥?

直觉会说:越大越好 - 很明显,强制 256 位随机值比 128 位更难。根据我们目前的理解,强制通过 128 位长字节的所有值都需要天文数量的能量,对于任何在合理时间内的人来说都是不现实的(看着你,NSA)。因此,决定基本上在无限和无限时间 2¹²⁸ 之间。

AES 实际上有三种不同的密钥大小,因为它被选为美国联邦政府的标注加密算法以用于联邦政府「包括军方」控制的各个领域。(…)因此,精明的军事首脑提出了应该有三个“安全级别”的想法,以便使用重量级方法加密最重要的秘密,但较低价值的数据可以用更实用,更轻量级的算法加密。(…)因此,NIST 决定正式遵守规定(要求三个关键尺寸),但也要做前瞻性的事(最低级别必须通过可遇见的技术不可攻破)(来源)。

论点如下:AES 加密消息可能不会被暴力破坏密钥破坏,而是通过其他较便宜的攻击(当前未知)。这些攻击对于 128 位密钥模式和 256 位模式一样有害,因此在这种情况下选择更大的密钥大小也无济于事。

所以基本上 128 位密钥对于大多数用例来说都足够安全,但量子计算机保护除外。同样使用比 256 位更快的 128 位加密。128 位密钥的密钥强度似乎可以更好的防止相关密钥攻击(但这与大多数实际用途无关)。

旁注:旁道攻击

旁道攻击是利用特定于某些实现的问题的攻击。加密密码方案本身不能有效地保护它们。简单的 AES 实现可能容易发生计时缓存攻击其他攻击

作为一个非常基本的例子:一个容易发生定时攻击的简单算法是一个比较两个秘密字节数组的 equals() 方法。如果 equals() 有一个快速返回,意味着在第一对不匹配的字节结束循环之后,攻击者可以测量 equals() 完成所需要的时间,并且可以一个字节一个字节的猜测,直到全部匹配为止。

使用快速返回可能受到定时攻击的代码

在这种情况下,一个修复方法是使用恒定时间等于。请注意,在类似于 JVM 等解释语言中编写常量时间代码往往并非易事。

针对 AES 的定时和缓存攻击不仅仅是理论上的,甚至可以通过网络进行实施。虽然防止旁道攻击主要是实施加密原语的开发人员关注的问题,但了解编码实践可能对整个例程的安全性有害是明智的。最一般的主题是,可观察到的与时间相关的行为不应该依赖于私密数据。此外,你应该仔细考虑要选择的实现方案。例如,使用带有 OpenJDK 的 Java 8+ 和默认的 JCA 提供程序应该在内部使用 Intel 的 AES-NI 指令集,该指令集通过恒定时间和在硬件中实现(同时仍具有良好的性能)来防止大多数时序和缓存攻击。Android 使用它的 AndroidOpenSSLProvider,内部可能会在硬件中使用 AES(ARM TrustZone),具体取决于 SoC。但我不相信它具有与 Intels pedant 相同的防护。但即使你改进硬件,也可以使用其他攻击向量,例如功率分析。存在专门用于防止大多数这些问题的专用硬件,即硬件安全模块(HSM)。不幸的是,这些设备的成本通常高达数千美元(有趣的是:你的基于芯片的信用卡也是 HSM)。

在 Java 和 Android 中实现 AES-GCM

最后它变得实用了。现在 Java 拥有我们需要的所有工具,但加密 API 可能不是最直接的。细心的开发人员也可能不确定要使用的长度/大小/默认值。注意:如果没有说明,所有内容都同样适用于 Java 和 Android

在我们的示例中,我们使用随机生成的 128 位密钥。传递 192 和 256 位长度的密钥时,Java 会自动选择正确的模式。但请注意,256 位加密通常需要在 JRE 中安装 无政策限制权限文件(Android 中无需安装)。

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, “AES”); 

然后我们必须创建我们的初始化向量。对于 CGM,NIST 建议使用 12 字节(非16字节!)随机字数组,因为它更快,更安全。请注意始终使用像 SecureRandom 这样的强伪随机数生成器(RNG)

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv); 

然后初始化你的密码。AES-GCM 模式应该适用于大多数现代 JRE 和 Android v2.3 以上版本虽然仅在 SDK 21+ 上可以完全正常运行)。如果碰巧不可用,请安装像 BouncyCastle 这样的自定义加密提供程序,但通常首选默认提供程序。我们选择 128 位大小的认证标签。

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); 

如果需要,添加可选的关联数据(例如元数据)

if (associatedData != null) {cipher.updateAAD(associatedData);
} 

加密;如果你正在加密大块数据,请研究 CipherInputStream,这样整个内容就无需加载到堆中。

byte[] cipherText = cipher.doFinal(plainText); 

现在将所有内容连接到一条消息。

ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array(); 

如果你需要字符串表示,可选用 Base64 来编码它。 Android 中有该编码的标准实现,JDK 仅从版本 8 开始(如果可能,我会避免使用 Apache Commons Codec,因为它很慢且实现混乱)。

这基本上就是加密。为了构造消息,IV 长度,IV,加密数据和认证标签被附加到单个字节数组。(在 Java 中,身份验证标记会自动附加到消息中,无法使用标准加密 API 自行处理)。

最佳事件是尽可能快地从内存中擦除加密密钥或 IV 等敏感数据。由于 Java 是一种具有自动内存管理的语言,因此我们无法保证以下内容能够预期工作,但在大多数情况下应该如此:

Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros 

注意不要覆盖仍在其他地方使用的数据。

现在到解密部分,它的工作原理类似加密,首先解构消息:

ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameterthrow new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText); 

小心验证输入参数,比如 IV 长度,因为攻击者可能会将长度值更改为如 2³¹,它会分配 2 GiB内存并可能很快填满你的堆,使得拒绝服务攻击变得微不足道。

初始化密码并添加可选的关联数据并解密:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
if (associatedData != null) {cipher.updateAAD(associatedData);
}
byte[] plainText= cipher.doFinal(cipherText); 

以上便是所有内容,如果你想查看一个完整的例子,请查看我托管到 Github 中的一个使用 AES-GCM 的项目 Armadillo

总结

我们需要三个属性来保护我们的数据:

  • 保密性:防止窃听者发现明文消息或有关明文消息的信息的能力。
  • 完整性:防止攻击者在合法用户未注意的情况下修改消息的能力。
  • 真实性:证明消息是由特定方生成并防止伪造新消息的能力。 这通常通过消息验证代码(MAC)提供。注意,真实性也意味着完整性。

具有 Galois/Counter(GCM)块模式的 AES 提供所有这些属性,并且相当容易使用,并且在大多数 Java/Android环境中都可用。 请考虑以下事项:

  • 使用永远不会与相同密钥一起使用的 12 字节初始化向量(使用像 SecureRandom 这样的强伪随机数生成器)。
  • 使用 128 位身份验证标记长度。
  • 使用 128 位密钥长度(你会没事的!)。
  • 将所有内容整合到一条消息中。

进一步阅读:

最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密:第2部分:AES-CBC + HMAC

参考资料:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密 的相关文章

随机推荐

  • android编程中遇到的关于 java.lang.NullPointerException错误的原因及解决办法

    LogCat中的错误提示如下 04 29 09 43 23 390 E AndroidRuntime 24967 FATAL EXCEPTION main 04 29 09 43 23 390 E AndroidRuntime 24967
  • 创建型模式5之3-Singleton单例模式的八种写法比较

    单例模式的八种写法比较 单例模式是最常用到的设计模式之一 熟悉设计模式的朋友对单例模式都不会陌生 一般介绍单例模式的书籍都会提到 饿汉式 和 懒汉式 这两种实现方式 但是除了这两种方式 本文还会介绍其他几种实现单例的方式 让我们来一起看看吧
  • 打发时光的102个网站

    1 看看自己具有哪个大明星的脸型 http www play analogia com cgi bin index 2 超有意思的Flash网站 虚拟办公 http agencynet com 3 亲自动手给美女画纹身 http www c
  • nginx 详解反向代理负载均衡

    什么是反向代理负载均衡 使用代理服务器可以将请求转发给内部的Web服务器 使用这种加速模式显然可以提升静态网页的访问速度 因此也可以考虑使用这种技术 让代理服务器将请求 均匀转发给多台内部Web服务器之一上 从而达到负载均衡的目的 这种代理
  • 【blender】材质球参数及各种问题

    目录 材质设置 1 共用一种材质 但是不同颜色 2 关联材质 3 无法绘制贴图 4 材质保存为资产 5 材质描边 材质设置 1 玻璃 1 共用一种材质 但是不同颜色 物体信息节点 gt 仅需改变物体颜色即可 2 关联材质 ctrl L 3
  • 网络安全——命令执行漏洞概述

    一 命令执行漏洞概述 1 基本定义 命令执行漏洞是指攻击者可以随意执行系统命令 分为远程命令执行 远程代码执行 和系统命令执行 2 原理 程序应用有时候需要调用一些执行系统命令的函数 如PHP中的system exec shell exex
  • SpringCloud组件之断路器Hystrix(hoxton版本)

    1 Hystrix 简介 在微服务架构中 根据业务来拆分成一个个的服务 服务与服务之间可以相互调用 RPC 在Spring Cloud可以用RestTemplate Ribbon和Feign来调用 为了保证其高可用 单个服务通常会集群部署
  • WinForm中如何实现panel和SplitContainer相结合进行布局呢

    相信大家都会在winform应用程序中进行布局 通常我们也会使用一下这种布局 如图 以上布局分别采用了Panel 黑色区域 和SplitContainer控件 白色区域 这布局相信大家耳熟能详了 比如VS2010不就是典型这样布局吗 但是需
  • 西瓜书之误差逆传播公式推导、源码解读及各种易混淆概念

    关键词 反向传播 BP caffe源码 im2col 卷积 反卷积 上池化 上采样 公式推导 以前看到一长串的推导公式就想直接跳过 今天上午莫名有耐心 把书上的公式每一步推导自己算一遍 感觉豁然开朗 遂为此记 sigmoid函数求导比rel
  • 最小二乘拟合,L1、L2正则化约束

    最小二乘法 又称最小平方法 是一种数学优化技术 它通过最小化误差的平方和寻找数据的最佳函数匹配 利用最小二乘法可以简便地求得未知的数据 并使得这些求得的数据与实际数据之间误差的平方和为最小 从维基百科中摘取的最小二乘的拟合曲线 解法 其中Y
  • TSI系统测量参数之:热膨胀

    一 TSI系统测量参数 1 轴向位移 2 盖振或瓦振 3 偏心 4 键相 5 零转速 6 轴向振动 7 相对热膨胀 胀差 8 绝对热膨胀 缸胀 二 各参数作用 4 绝对热膨胀 汽轮机在开机过程中由于受热使其汽缸膨胀 如果膨胀不均匀就会使汽缸
  • 辅助汇编学习记录2

    通用寄存器 EAX EBX ECX EDX ESI EDI ESP EBP 它 们 的低 16 位就是 8086 的 AX BX CX DX SI DI SP BP 它们的含义如下 EAX 累加器 EBX 基址寄存器 Base ECX 计数
  • C语言中的短路现象

    短路现象1 比如有以下表达式 a b c 只有a为真 非0 才需要判断b的值 只有a和b都为真 才需要判断c的值 举例 求最终a b c d的值 main int a b c d a 0 b 1 c 2 d a b c printf a d
  • 桥接模式与策略模式的区别

    文章转载自 http www blogjava net wangle archive 2007 04 25 113545 html 桥接 Bridge 模式是结构型模式的一种 而策略 strategy 模式则属于行为模式 以下是它们的UML
  • 【生信】全基因组关联分析(GWAS)原理

    生信 全基因组关联分析 GWAS 原理 文章的文字 图片 代码部分 全部来源网络或学术论文 文章会持续修缮更新 仅供大家学习使用 目录 生信 全基因组关联分析 GWAS 1 前提知识介绍 1 1 最小二乘法 1 2 GWAS的数学原理 1
  • 【笔记】软件测试06——Web自动化

    阅读 石墨文档 七 web自动化测试 GUI自动化测试学习内容 了解自动化测试的相关概念 掌握Selenium Webdriver常用API 掌握自动化测试中的元素定位方法 掌握自动化测试中的元素操作 掌握自动化测试断言操作 掌握unitt
  • 使用合宙Air700e点亮一个LED灯(lua)

    相信很多朋友和我一样都团了9 9的air700e开发板 我猜有很多朋友都是买来吃灰的吧 包括我也是一样 网络上的相关资料并不是很丰富 对于像我这样的小白来说不是很友好 今天给大家演示一下使用air700e演示点灯大法 通常我们见到使用通信模
  • HTML常用标签合集

    今天来讲讲有关html的常用标签 嘎嘎有用 嘎嘎好用 目录 HTML常用标签 一 首先来讲第一种 标题标签 h1 h6 二 第二种 段落标签 p 三 第三种 hgroup标签 四 第四种 强调标签 em strong 五 第五种 引用标签
  • 关于Android向前兼容和向后兼容问题的理解

    最近在和别人交流的的时候涉及到Android开发向前兼容和向后兼容的问题一头雾水 于是乎定下心来好好研究了下 虽然所知也只是些皮毛 但是也总比啥也不知道的好 所以在此总结 一 向前兼容 1 何谓向前兼容 google公司在不断的发步新的an
  • [译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密

    原文地址 Security Best Practices Symmetric Encryption with AES in Java and Android 最佳安全实践 在 Java 和 Android 中使用 AES 进行对称加密 我将