Java JCE Cipher 详解

本文是对 Java 语言安全模块 JCE( Java Cryptography Extension,Java 密码学扩展)中 javax.crypto.Cipher 核心类的详细介绍及使用说明。


目录

  • Cipher 简介
  • 初始化 transformation
    • 算法(algorithm)
    • 工作模式(mode)
    • 填充模式(padding)
  • Cipher 属性和方法
    • 主要属性
    • 主要方法
  • 工作流程
  • 附录

Cipher 简介

javax.crypto.Cipher 是 JCE ( Java Cryptography Extension,Java 密码学扩展)的核心,提供基于多种加解密算法的加解密功能。


初始化 transformation

初始化 transformation(转换模式)由三部分组成,格式:算法/工作模式/填充模式(algorithm/mode/padding),如 DES/ECB/PKCS5Padding

算法(algorithm)

算法(algorithm),必填项,指具体加密算法的英文名称字符串,如 DESAESSHA-256RSA 等。

工作模式(mode)

工作模式(mode),非必填项,默认为 ECB,主要针对分组密码,分组密码是将明文消息编码后的数字序列(简称明文数字序列)划分成长度为 n 的组(可看成长度为 n 的矢量),每组分别在密钥控制下变换成等长的输出数字序列(简称密文数字序列)。
工作模式的出现主要基于以下原因:

  • 当需要加密的明文长度十分大时(如大文件内容),由于硬件或性能原因需要分组加密。
  • 多次使用相同的密钥对多个分组加密会引发安全问题。

本质上来说,工作模式是一项增强密码算法或使算法适应具体应用的技术,例如将分组密码应用于数据块组成的序列或数据流。目前已定义的工作模式有以下 5 种:

  • ECB(Electronic Code Book,电子密码本),用相同的密钥分别对明文分组独立加密。
  • CBC(Cipher Block Chaining,密码分组链接),加密算法的输入是上一个密文组和下一个明文组的异或。
  • CFB(Cipher Feed Back,密文反馈),一次处理 s 位,上一个密文组作为加密算法的输入,产生的伪随机数输出与明文组异或后作为下一个密文组。
  • OFB(Output Feed Back,输出反馈),与 CFB 类似,只是加密算法的输入是上一次加密的输出,并且使用整个分组。
  • CTR(Counter,计数器),每个明文分组都与一个经过加密的计数器相异或,对每个后续分组计数器递增。
填充模式(padding)

填充模式(padding),非必填项,默认为 PKCS5Padding,块加密算法要求,原文数据长度为固定块大小的整数倍,如果原文数据长度大于固定块大小,则需要在固定块填充数据直至整个块的数据是完整的。例如如果约定块长度是 128,实际需要加密的原文长度是 129,则需要分为两个加密块,第二个加密块需要填充 127 位数据,填充模式决定了如果填充。
对原文进行填充主要基于以下原因:

  • 安全性,由于对原始数据进行了填充,攻击者很难找到真正的原文位置。
  • 由于块加密算法要求原文数据长度为固定块大小的整数倍,如果加密原文不满足这个条件,则需要在加密前填充原文数据至固定块大小的整数倍。
  • 填充也为发送方与接收方提供了一种标准的形式以约束加密原文的大小。只有加解密双方知道填充方式,才可知道如何准确移去填充的数据并进行解密。

常见的填充方式有 5 种:

  • 填充数据为填充字节序列的长度
    填充字符串由一个字节序列 L 组成,每个字节填充该字节序列 L 的长度。
    如固定块大小为 8,原文数据长度为 9,则填充字节序列长度为 2 * 8 - 9 = 7,即填充字节数为 0x07
    原文数据:FF FF FF FF FF FF FF FF FF
    填充后数据:FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07
  • 填充数据为 0x80 后加 0x00
    填充字符串的第一个字节数是 0x80,后面的每个字节是 0x00
    原文数据:FF FF FF FF FF FF FF FF FF
    填充后数据:FF FF FF FF FF FF FF FF FF 80 00 00 00 00 00 00
  • 填充数据的最后一个字节为填充字节序列的长度
    填充字符串的最后一个字节为该序列的长度,而前面的字节可以是 0x00,也可以是随机的字节序列。
    原文数据:FF FF FF FF FF FF FF FF FF
    填充后数据:FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07FF FF FF FF FF FF FF FF FF 0A B0 0C 08 05 09 07
  • 填充数据为空格
    填充字符串的每个字节为空格对应的字节数 0x20
    原文数据:FF FF FF FF FF FF FF FF FF
    填充后数据:FF FF FF FF FF FF FF FF FF 20 20 20 20 20 20 20
  • 填充数据为 0x00
    填充字符串的每个字节为 0x00
    原文数据:FF FF FF FF FF FF FF FF FF
    填充后数据:FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00

Java 原生支持的填充模式:

  • NoPadding:不启用填充模式
  • ISO10126Padding:XML 加密语法和处理文档中有详细描述
  • OAEPPadding, OAEPWith<digest>And<mgf>Padding:PKCS1 中定义的最优非对称加密填充方案,digest 代表消息摘要类型,mgf 代表掩码生成函数,例如:OAEPWithMD5AndMGF1PaddingOAEPWithSHA-512AndMGF1Padding
  • PKCS1Padding:PKCS1,RSA算法使用
  • PKCS5Padding:PKCS5,RSA算法使用
  • SSL3Padding:见SSL Protocol Version 3.0的定义

其它填充模式需要依赖于第三方支持。

JCE 支持的 Cipher 配置:

算法 工作模式 填充模式
AES EBCCBCPCBCCTRCTSCFBCFB8-CFB128 NoPaddingISO10126PaddingPKCS5Padding
AESWrap EBC NoPadding
ARCFOUR EBC NoPadding
BlowfishDESDESedeRC2 EBCCBCPCBCCTRCTSCFBCFB8-CFB128 NoPaddingISO10126PaddingPKCS5Padding
DESedeWrap CBC NoPadding
PBEWithMD5AndDESPBEWithMD5AndTripleDESPBEWithSHA1AndDESedePBEWithSHA1AndRC2_40 CBC PKCS5Padding
RSA ECBNONE NoPaddingPKCS1Padding

Cipher 属性和方法

主要属性
属性名 数据类型 说明
ENCRYPT_MODE 整型 加密模式,用于初始化
DECRYPT_MODE 整型 解密模式,用于初始化
WRAP_MODE 整型 包装密钥模式,用于初始化
UNWRAP_MODE 整型 解包装密钥模式,用于初始化
PUBLIC_KEY 整型 解包装密钥模式下指定密钥类型为公钥
PRIVATE_KEY 整型 解包装密钥模式下指定密钥类型为私钥
SECRET_KEY 整型 解包装密钥模式下指定密钥类型为密钥,主要用于不是非对称加密的密钥(只有一个密钥,不包含私钥和公钥)
主要方法
  • Cipher.getInstance
    Cipher 提供了 3 个静态工厂方法用于生成其实例。这 3 个方法都有一个核心参数 transformation。其中 2 个方法需要 java.security.Provider 全名或其实例对象,因为 Cipher 要从对应供应商中获取指定转换模式的实现,不带此参数的方法会从现成的所有 java.security.Provider 中取出第一个满足 transformation 的服务并从中实例化 CipherSpi(要理解 Cipher 委托到内部持有的 CipherSpi 实例完成具体的加解密功能)。实际上Cipher实例的初始化必须依赖于转换模式和提供商。

  • init
    此方法主要用于初始化 Cipher,共有 8 个重载方法:
    init(int opmode, Certificate certificate)
    init(int opmode, Certificate certificate, SecureRandom random)
    init(int opmode, Key key)
    init(int opmode, Key key, AlgorithmParameters params)
    init(int opmode, Key key, AlgorithmParameterSpec params)
    init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random)
    init(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
    init(int opmode, Key key, SecureRandom random)
    参数说明:

    • opmode:操作模式,必填参数,可选值包括 ENCRYPT_MODEDECRYPT_MODEWRAP_MODEUNWRAP_MODE
    • Key:如果是对称加密则对应类型是 SecretKey,如果是非对称加密则对应类型是 PublicKeyPrivateKey
    • SecureRandom:随机源,有些算法需要每次加密结果都不相同,这个时候需要依赖系统或者传入的随机源,一些要求每次加解密结果相同的算法如 AES 不能使用此参数。
    • Certificate:带有密钥的证书实现。
  • wrap & unwrap
    wrap 方法用于包装一个密钥,使用的时候需要注意 Cipher 的 opmode 要初始化为 WRAP_MODE
    unwrap 方法用于解包装一个密钥,使用的时候需要注意 Ciphe r的 opmode 要初始化为 UNWRAP_MODE,在调用 unwrap 方法时候,需要指定之前包装密钥的算法和 Key 的类型。
    其实 wrapunwrap 是一个互逆的操作:

    • wrap 方法的作用是把原始的密钥通过某种加密算法包装为加密后的密钥,这样就可以避免在传递密钥的时候泄漏了密钥的明文。
    • unwrap 方法的作用是把加密后的密钥解包装为原始的密钥,得到密钥的明文。
  • update
    update 方法主要用于部分加密或者部分解密,至于是加密还是解密取决于 Cipher 初始化时的 opmode 参数。
    原理:依赖于一个输入的缓冲区(带有需要被加密或者被解密的数据),返回值或者参数是一个输出的缓冲区,一些额外的参数可以通过偏移量和长度控制加密或者解密操作的数据段。部分加密或者解密操作完毕后,必须要调用 Cipher.doFinal() 方法来结束加密或者解密操作。

  • doFinal
    doFinal 共有 7 个重载方法:

    1. doFinal()
      • 结束多部分加密或者解密操作。
      • 此方法需要在update调用链执行完毕之后调用,返回的结果是加密或者解密结果的一部分。
      • 此方法正常调用结束之后Cipher会重置为初始化状态。
    2. doFinal(byte[] input)
      • 结束单部分加密或者解密操作。
      • 此方法接收需要加密或者解密的完整报文,返回处理结果。
      • 此方法正常调用结束之后Cipher会重置为初始化状态。
    3. doFinal(byte[] output, int outputOffset)
      • 结束多部分加密或者解密操作。
      • 此方法需要在update调用链执行完毕之后调用,传入的output作为缓冲区接收加密或者解密结果的一部分。
      • 此方法正常调用结束之后Cipher会重置为初始化状态。
    4. doFinal(byte[] input, int inputOffset, int inputLen)
      • 结束单部分或者多部分加密或者解密操作。
      • 参数inputOffset为需要加解密的报文byte数组的起始位置,inputLen为需要加密或者解密的字节长度。
      • 此方法正常调用结束之后Cipher会重置为初始化状态。
    5. doFinal(byte[] input, int inputOffset, int inputLen, byte[] output)
      • 结束单部分或者多部分加密或者解密操作。
      • 参数inputOffset为需要加解密的报文byte数组的起始位置,inputLen为需要加密或者解密的字节长度,output用于接收加解密的结果。
      • 此方法正常调用结束之后Cipher会重置为初始化状态。
    6. doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
      • 结束单部分或者多部分加密或者解密操作。
      • 参数inputOffset为需要加解密的报文byte数组的起始位置,inputLen为需要加密或者解密的字节长度,output用于接收加解密的结果,outputOffset用于设置output的起始位置
      • 此方法正常调用结束之后Cipher会重置为初始化状态。
    7. doFinal(ByteBuffer input, ByteBuffer output)
      • 结束单部分或者多部分加密或者解密操作。
      • 参数input为输入缓冲区,output为输出缓冲区。
      • 此方法正常调用结束之后Cipher会重置为初始化状态。

doFinal()主要功能是结束单部分或者多部分加密或者解密操作。

  • 单部分加密或者解密适用于需要处理的报文长度较短无需分块的情况,这个时候直接使用byte[] doFinal(byte[] input)方法即可。
  • 多部分加密或者解密适用于需要处理的报文长度长度较大,需要进行分块的情况,这个时候需要调用多次update方法变体进行部分块的加解密,最后调用doFinal方法变体进行部分加解密操作的结束。

举个例子,例如处理块的大小为 8,实际需要加密的报文长度为 23,那么需要分三块进行加密,前面 2 块长度为 8 的报文需要调用 update 进行部分加密,部分加密的结果可以从 update 的返回值获取到,最后长度为 7 (一般会填充到长度为块长度 8)的报文则调用 doFinal 进行加密,结束整个部分加密的操作。另外,值得注意的是只要 Cipher 正常调用完任一个 doFinal 变体方法(过程中不抛出异常),那么 Cipher 会重置为初始化状态,可以继续使用,这个可复用的特性可以降低创建 Cipher 实例的性能损耗。


工作流程

Cipher 工作流程

  1. Cipher.getInstance 创建一个 Cipher 实例:
    • 如果有 java.security.Provider 全名或其实例对象,则从对应供应商中获取指定转换模式的实现实例化 CipherSpi
    • 如果没有此参数,则从现成的所有 java.security.Provider 中取出第一个满足 transformation 的服务并从中实例化 CipherSpi
      在${JAVA_HONE}/jre/lib/security中的java.security文件中可以看到默认加载的提供商。如果需要添加额外或者自实现的Provider,可以通过java.security.Security的静态方法addProvider添加。
  2. 通过 Cipher 实例的 init 方法初始化,主要参数是 opmode 和密钥。
  3. 根据初始化的方式和是否需要分组进行处理
    • 需要分组处理:update -> doFinal
    • 不需要分组处理:执行 wrap -> unwrap -> doFinal
      doFinal 方法返回最终结果。

附录

参考:JDK安全模块JCE核心Cipher使用详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容