java网络学习之 jca 之cipha 引擎类介绍(14)

Cipher类

Cipher类提供用于加密和解密的加密密码的功能。 加密是取数据(称为明文)和密钥的过程,并且产生对不知道密钥的第三方毫无意义的数据(密文)。 解密是相反的过程:取密文和密钥并产生明文。 
加密

对称与非对称密码学

有两种主要类型的加密:对称(也称为密钥)和非对称(或公钥密码)。 在对称密码学中,同一个密钥既能加密也能解密数据。 保持密钥私密对保持数据保密至关重要。 另一方面,非对称加密使用公钥/私钥对来加密数据。 用一个密钥加密的数据与另一个密钥解密。 用户首先生成公钥/私钥对,然后将公钥发布在任何人都可以访问的可信数据库中。 希望与该用户安全通信的用户使用检索到的公钥来加密数据。 只有私钥的持有者才能解密。 保密私钥对此方案至关重要。

不对称算法(如RSA)通常比对称算法慢得多。 这些算法不能有效地保护大量的数据。 实际上,不对称算法被用来交换较小的秘密密钥,用来初始化对称算法。

流加密与分组加密

有两种主要类型:块加密和流加密。 块加密(分组加密)一次处理整个块,通常是多个字节的长度。 如果没有足够的数据来创建完整的输入块,则必须填充数据:也就是说,在加密之前,必须添加虚拟字节以使密码块大小成倍数。 这些字节在解密阶段被剥离。 填充既可以由应用程序完成,也可以通过初始化密码来使用填充类型,例如“PKCS5PADDING”。 相比之下,流加密一次只能处理一个小单元(通常是一个字节,甚至一点点)的传入数据。 这允许密码处理任意数量的数据而不用填充。

操作模式

当使用简单的分组密码进行加密时,两个相同的明文块将总是产生相同的密文块。如果他们注意到重复文本块,那么试图破解密文的密码分析者将会有更容易的工作。为了增加文本的复杂性,反馈模式使用前面的输出块在应用加密算法之前改变输入块。第一个块需要一个初始值,这个值被称为初始化向量(IV)。由于IV在加密之前只是简单地改变数据,所以IV应该是随机的,但不一定需要保密。有多种模式,例如CBC(密码块链接),CFB(密码反馈模式)和OFB(输出反馈模式)。 ECB(电子码本模式)是一种不受块位置或其他密文块影响的模式。因为如果ECB密文使用相同的明文/密钥,ECB密文是相同的,这种模式通常不适合加密应用,不应该使用。

更新的密码模式,例如带有关联数据的认证加密(AEAD)(例如,伽罗瓦/计数器模式(GCM)),可以对数据进行加密并同时验证结果信息。 在计算生成的AEAD标记(Mac)期间可以使用附加关联数据(AAD),但是这个AAD数据不会以密文的形式输出。 (例如,某些数据可能不需要保密,但应该计算标签计算以检测修改。)Cipher.updateAAD()方法可用于在标签计算中包含AAD。

使用GCM模式的AES密码

使用GCM的AES密码是一种AEAD密码,与非AEAD密码具有不同的使用模式。 除了常规数据外,还需要AAD,这是可选的加密/解密,但AAD必须在数据加密/解密之前提供。 另外,为了安全地使用GCM,调用者不应该重复使用密钥和IV组合来进行加密。 这意味着每次加密操作时,密码对象应该用不同的一组参数显式地重新初始化。

SecretKey myKey = ... ;
byte[] myAAD = ... ;
byte[] plainText = ... ;
int myTLen = ... ;
byte[] myIv = ... ;

GCMParameterSpec myParams = new GCMParameterSpec(myTLen, myIv); 
Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); 
c.init(Cipher.ENCRYPT_MODE, myKey, myParams); 
// AAD is optional, if present, it must be supplied before any update/doFinal calls. c.updateAAD(myAAD); // if AAD is non-null 
byte[] cipherText = new byte[c.getOutputSize(plainText.length)]; 
c.doFinal(plainText, 0, plainText.length, cipherText); // conclusion of encryption operation 
// To decrypt, same AAD and GCM parameters must be supplied c.init(Cipher.DECRYPT_MODE, myKey, myParams); 
c.updateAAD(myAAD); 
byte[] recoveredText = c.doFinal(cipherText); 
// MUST CHANGE IV VALUE if the same key were to be used again for encryption byte[] newIv = ...; 
myParams = new GCMParameterSpec(myTLen, newIv);

 秘钥:

 

一些算法如AES和RSA允许不同长度的密钥,但其他算法则是固定的,如3DES。 使用更长的密钥进行加密通常意味着对消息恢复的更强的抵抗力。 像往常一样,安全和时间之间有一个折衷,所以选择适当的密钥长度。

大多数算法使用二进制密钥。 即以十六进制表示,大多数人类也无法记忆长序列的二进制数字。 字符密码更容易记忆。 由于字符密码通常是从少量字符中选择的(例如[a-zA-Z0-9]),因此已经定义了诸如“基于密码的加密”(PBE)等协议,这些协议使用字符密码并生成强二进制密钥。 为了使攻击者从口令到密钥的任务非常耗时(通过所谓的“字典式攻击”,其中常用字典字 - 值映射是预先计算的),大多数PBE实现将以随机数混合, 被称为盐,以增加关键的随机性。

 

  • 创建Cipher对象

密码对象是通过使用密码getInstance()静态工厂方法之一获得的。 在这里,算法名称与其他引擎类稍有不同,因为它不仅指定算法名称,而且指定“变换”。 转换是一个字符串,它描述了在给定输入上执行的操作(或操作集)以产生一些输出。 变换总是包括密码算法的名称(例如,AES),并且可以跟随模式和填充方案。

转型的形式是:

  • “algorithm/mode/padding” or
  • “algorithm”

例如,以下是有效的转换:

    "AES/CBC/PKCS5Padding"

    "AES"

如果只指定了一个转换名称,系统将确定在环境中是否有所需的转换实现,如果有多个转换名称,则返回一个首选项。

如果同时指定了转换名称和包提供者,系统将确定所请求的包中是否存在所请求转换的实现,如果没有,则抛出异常。

建议使用完全指定算法,模式和填充的转换。 如果不这样做,提供者将使用默认值。 例如,SunJCE和SunPKCS11提供程序将ECB用作默认模式,将PKCS5Padding用作许多对称密码的默认填充。

这意味着在SunJCE提供商的情况下:

Cipher c1 = Cipher.getInstance("AES/ECB/PKCS5Padding");

Cipher c1 = Cipher.getInstance("AES");

是等同的语句。

**注意:**ECB模式是最简单的块密码模式,并且是JDK / JRE中的默认模式。 ECB适用于单个数据块,但绝对不应该用于多个数据块。

使用CFB和OFB等模式,分组密码可以以小于密码实际块大小的单位加密数据。 在请求这种模式时,可以通过在“AES / CFB8 / NoPadding”和“AES / OFB32 / PKCS5Padding”转换中将模式名称附加到模式名称来一次指定要处理的位数。 如果没有指定这样的号码,则使用提供者特定的默认值。 (例如,SunJCE提供程序使用默认值为128位的AES)。因此,可以使用8位模式(如CFB8或OFB8)将块密码转换为面向字节的流密码。

初始化Cipher对象

通过getInstance获得的Cipher对象必须初始化为四种模式之一,在Cipher类中定义为最终整数常量。 这些模式可以通过它们的符号名称来引用,这些符号名称将在下面显示,同时还会描述每种模式的用途:

ENCRYPT_MODE

数据加密.

DECRYPT_MODE

数据解密.

WRAP_MODE

将java.security.Key包装为字节,以便可以安全地传输密钥.

UNWRAP_MODE

将之前包装的密钥解包到java.security.Key对象中.

每个密码初始化方法都采用操作模式参数(opmode),并初始化该模式的密码对象。 其他参数包括包含密钥(certificate)的密钥(key)或证书,算法参数(params)以及随机源(random)。

要初始化Cipher对象,请调用以下init方法之一:

    public void init(int opmode, Key key);

    public void init(int opmode, Certificate certificate);

    public void init(int opmode, Key key, SecureRandom random);

    public void init(int opmode, Certificate certificate,
                     SecureRandom random);

    public void init(int opmode, Key key,
                     AlgorithmParameterSpec params);

    public void init(int opmode, Key key,
                     AlgorithmParameterSpec params, SecureRandom random);

    public void init(int opmode, Key key,
                     AlgorithmParameters params);

    public void init(int opmode, Key key,
                     AlgorithmParameters params, SecureRandom random);
  • 如果需要参数的密码对象(例如,初始化向量)被初始化为加密,并且没有参数提供给初始化方法,则底层密码实现本身应该提供所需的参数,或者通过生成随机参数或者通过使用 默认的,特定于提供者的参数集合。

但是,如果需要参数的Cipher对象被初始化为解密,并且没有参数提供给init方法,则将引发InvalidKeyException或InvalidAlgorithmParameterException异常,具体取决于所使用的init方法。

必须使用与加密相同的参数进行解密。

请注意,当一个Cipher对象被初始化时,它将失去所有先前获得的状态。 换句话说,初始化一个Cipher就相当于创建一个新的Cipher实例,并初始化它。 例如,如果密码首先被初始化为用给定密钥进行解密,然后初始化用于加密,则在解密模式下将失去获得的任何状态。

数据加解密

数据可以在一个步骤(单部分操作)或多个步骤(多部分操作)中加密或解密。 如果事先不知道数据将要运行多长时间,或者数据太长而无法一次存储在内存中,则多部分操作非常有用。

要在一个步骤中加密或解密数据,请调用其中一个doFinal方法:

    public byte[] doFinal(byte[] input);

    public byte[] doFinal(byte[] input, int inputOffset, int inputLen);

    public int doFinal(byte[] input, int inputOffset,
                       int inputLen, byte[] output);

    public int doFinal(byte[] input, int inputOffset,
                       int inputLen, byte[] output, int outputOffset)

要以多个步骤加密或解密数据,请调用其中一种更新方法:

    public byte[] update(byte[] input);

    public byte[] update(byte[] input, int inputOffset, int inputLen);

    public int update(byte[] input, int inputOffset, int inputLen,
                      byte[] output);

    public int update(byte[] input, int inputOffset, int inputLen,
                      byte[] output, int outputOffset)

多部分操作必须由上述doFinal方法之一终止(如果最后一步仍有一些输入数据),或者通过以下doFinal方法之一(如果没有输入数据留给最后一步):

    public byte[] doFinal();

    public int doFinal(byte[] output, int outputOffset);

如果填充(或非填充)已被请求作为指定转换的一部分,则所有doFinal方法都将处理任何必要的填充(或非填充)。

调用doFinal会将Cipher对象重置为通过调用init初始化时的状态。 也就是说,Cipher对象被重置并且可用于加密或解密(取决于在对init的调用中指定的操作模式)更多的数据。

封装和解包密钥

包装钥匙可以将钥匙从一个地方安全地转移到另一个地方。

wrap / unwrap API使得编写代码更方便,因为它直接处理关键对象。 这些方法还可以安全地传输基于硬件的密

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值