概要
最近再封装一个加密接口,类似RSA,于是决定深入研究一下Cipher这个Java的加密包。
RSA示例
网上copy的一段代码
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return encoder.encodeToString(encryptedData);
}
几个关键过程:
1 获取算法
Cipher cipher = Cipher.getInstance("RSA");
2 算法初始化:
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
3 分段加密:
RSA加密有长度限制
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
下面重点看下这几个方法吧
Cipher.getInstance
调用顺序
1 关键代码:Cipher getInstance(String var0)
-> List var1 = getTransforms(var0);
-> String[] var1 = tokenizeTransformation(var0);
-> StringTokenizer var3 = new StringTokenizer(var0, "/");
这个方法中去取真实的算法名称["RSA"]
为什么会有这个,网上搜了一下原来这个接口还有这种调用方式:
Cipher c = Cipher.getInstance("DES/CBC/PKCS5Padding");
对应: “算法/模式/填充”
所以String[] var1 = tokenizeTransformation(var0);得到的可能是["算法",“模式”,“填充”]
对应的上层调用的方法getTransforms内部,就是根据是否有模式和填充算法分别做如下处理:
核心代码如下
if (var3 == null && var4 == null) {
// 【无】 模式和填充参数
Cipher.Transform var6 = new Cipher.Transform(var2, "", (String)null, (String)null);
return Collections.singletonList(var6);
} else {
// 【有】 模式和填充参数
ArrayList var5 = new ArrayList(4);
var5.add(new Cipher.Transform(var2, "/" + var3 + "/" + var4, (String)null, (String)null));
var5.add(new Cipher.Transform(var2, "/" + var3, (String)null, var4));
var5.add(new Cipher.Transform(var2, "//" + var4, var3, (String)null));
var5.add(new Cipher.Transform(var2, "", var3, var4));
return var5;
}
可以看出其中调用的: Cipher.Transform方法,就是根据模式和填充来组合Cipher.Transform对象,
如果只有算法参数,返回Collections.singletonList(var6)
如果有多个参数,返回一个ArrayList,其中用到了好多参数的组合,至于为什么这么组合,暂时不深究。
至此,我们看到了List var1 = getInstance(String var0)中第一行getTransforms(var0);所做的事情,继续往下。
var1中每一行是"算法",“模式”,“填充”,但是在下面的调用中
var1--> var2,var2 是在1的基础上转化成了service对象,简单来说就是var1,加上一个type属性编程service

然后通过下面这个语句,从 ProviderList中取到对应的服务。(provider是加密服务的注册地址,这个包中比较重要的一种方式,之后介绍。)
List var11 = GetInstance.getServices(var2);
得到了模式,算法,填充和服务之后,通过以下两种方式返回Cipher实例
if (var8 == 2) {
return new Cipher((CipherSpi)null, var6, var12, var0, var1);
}
try {
CipherSpi var9 = (CipherSpi)var6.newInstance((Object)null);
var7.setModePadding(var9);
return new Cipher(var9, var6, var12, var0, var1);
} catch (Exception var10) {
var5 = var10;
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
因为是加密,所以Cipher.ENCRYPT_MODE = 1,其余可选数值
public static final int DECRYPT_MODE = 2;
public static final int WRAP_MODE = 3;
public static final int UNWRAP_MODE = 4;
public static final int PUBLIC_KEY = 1;
public static final int PRIVATE_KEY = 2;
public static final int SECRET_KEY = 3;
内部逻辑也是在选取provider,以及选择一些默认的padding,permission,,provider是这个包中提供的机制,一个provider对应一个已注册的算法的服务,这个模式暂时不深究
cipher.doFinal
doFinal(data.getBytes(), offset, inputLen - offset)
-> this.spi.engineDoFinal 一个抽象接口
-> engineDoFinal ,rsaCipher中找到实现类
-> this.doFinal();
-> RSACore.rsa(var1, this.publicKey);
注意一下,rsa算法加密长度限制要小于N的长度限制,
比如N是1024位,128字节,理论上原文长度限制128.
实际算法包的长度限制是117。网上资料是给padding预留11位。
RSAPadding类中代码上确实有相关的操作

但是maxDataSize字段也有其他的赋值方法,这里不深究各个条件下的长度限制了。
网友评论