javax.crypto.Cipher 源码学习笔记

该类是JCE用来加密的引擎类,支持对称和非对称加密。该类的介绍可以参考:[[译]JCA参考指南(二):核心类和接口](https://xshandow.gitee.io/201...

最近看了下OpenJDK中的Cipher源码,现在介绍一下Cipher的内部实现。

1.Cipher函数

创建Cipher对象:getInstance()

操作:init()、update()、doFinal()

GCM|CCM:updateAAD()

其中:getInstance()方法是在Cipher中操作的方法,其他几个都使用spi.engin**()执行。

状态变化:

2.内部类Transform

内部类Transform是用来解析getInstanse()中传入的transform字符串的。

setModePadding(CipherSpi spi)中通过spi.engineSetMode(mode|pad)设置,反馈模式和补丁方案。

判断Provider.service是否支持提供的mod和pad.

3.getInstanse()

getInstanse()传入的转换的格式如下:

  • “algorithm/mode/padding” or(标准名称)
  • “algorithm”

只传入transformation:

public static final Cipher getInstance(String transformation)
        throws NoSuchAlgorithmException, NoSuchPaddingException
{
    //获取Transform列表,带alg
    List transforms = getTransforms(transformation);
    List cipherServices = new ArrayList(transforms.size());
    
    for (Iterator t = transforms.iterator(); t.hasNext(); ) {
        Transform transform = (Transform)t.next();

        //transform = alg + sufix(/[mode] + /[padding])
        //有四种形式:mode和padding是可选的
        cipherServices.add(new ServiceId("Cipher", transform.transform));
    }
    //获取支持Transform(alg+suffix)的List<Service>
    //所有的Provider全部遍历一遍
    List services = GetInstance.getServices(cipherServices);
    // make sure there is at least one service from a signed provider
    // and that it can use the specified mode and padding
    Iterator t = services.iterator();
    Exception failure = null;
    while (t.hasNext()) {
        Service s = (Service)t.next();
        if (JceSecurity.canUseProvider(s.getProvider()) == false) {
            continue;
        }
        //返回 transforms.suffix和算法的后缀一样的第一个Transform
        Transform tr = getTransform(s, transforms);
        if (tr == null) {
            // should never happen
            continue;
        }
        //是否支持
        int canuse = tr.supportsModePadding(s);
        if (canuse == S_NO) {
            // does not support mode or padding we need, ignore
            continue;
        }
        //找到第一个结束
        if (canuse == S_YES) {
            return new Cipher(null, s, t, transformation, transforms);
        } else { // S_MAYBE, try out if it works
            try {
                CipherSpi spi = (CipherSpi)s.newInstance(null);
                tr.setModePadding(spi);
                return new Cipher(spi, s, t, transformation, transforms);
            } catch (Exception e) {
                failure = e;
            }
        }
    }
    throw new NoSuchAlgorithmException
        ("Cannot find any provider supporting " + transformation, failure);
}

另一种同时传入Provider:

 public static final Cipher getInstance(String transformation,Provider provider)
        throws NoSuchAlgorithmException, NoSuchPaddingException
{
    if (provider == null) {
        throw new IllegalArgumentException("Missing provider");
    }
    Exception failure = null;
    List transforms = getTransforms(transformation);
    boolean providerChecked = false;
    String paddingError = null;
    for (Iterator t = transforms.iterator(); t.hasNext();) {
        Transform tr = (Transform)t.next();
        //直接搜该provider是否支持
        Service s = provider.getService("Cipher", tr.transform);
        if (s == null) {
            continue;
        }
        if (providerChecked == false) {
            // for compatibility, first do the lookup and then verify
            // the provider. this makes the difference between a NSAE
            // and a SecurityException if the
            // provider does not support the algorithm.
            Exception ve = JceSecurity.getVerificationResult(provider);
            if (ve != null) {
                String msg = "JCE cannot authenticate the provider "
                    + provider.getName();
                throw new SecurityException(msg, ve);
            }
            providerChecked = true;
        }
        if (tr.supportsMode(s) == S_NO) {
            continue;
        }
        if (tr.supportsPadding(s) == S_NO) {
            paddingError = tr.pad;
            continue;
        }
        try {
            CipherSpi spi = (CipherSpi)s.newInstance(null);
            tr.setModePadding(spi);
            Cipher cipher = new Cipher(spi, transformation);
            cipher.provider = s.getProvider();
            cipher.initCryptoPermission();
            return cipher;
        } catch (Exception e) {
            failure = e;
        }
    }

    // throw NoSuchPaddingException if the problem is with padding
    if (failure instanceof NoSuchPaddingException) {
        throw (NoSuchPaddingException)failure;
    }
    if (paddingError != null) {
        throw new NoSuchPaddingException
            ("Padding not supported: " + paddingError);
    }
    throw new NoSuchAlgorithmException
            ("No such algorithm: " + transformation, failure);
}

  • 第一个getInstance(),将transform转换成内部Transform,遍历所有的Provider,查询到第一个支持transform的Service,然后new Cipher().
  • 第二种getInstance(),将transform转换成内部Transform,直接通过provider.getService("Cipher", tr.transform)查询是否支持transform,然后new Cipher().

注意:

  • tr.transform是通过下面介绍的的函数获得的。
  • 查询service时,也会查询别名是否等于tr.transform。

4.List getTransforms(String transformation)

/**
 * 获取Transform的List列表
 */
private static List getTransforms(String transformation)
        throws NoSuchAlgorithmException {
    String[] parts = tokenizeTransformation(transformation);

    String alg = parts[0];
    String mode = parts[1];
    String pad = parts[2];
    if ((mode != null) && (mode.length() == 0)) {
        mode = null;
    }
    if ((pad != null) && (pad.length() == 0)) {
        pad = null;
    }

    //Transform 仅有alg
    if ((mode == null) && (pad == null)) {
        // DES
        Transform tr = new Transform(alg, "", null, null);
        return Collections.singletonList(tr);
    } else { 
        // Transform = alg/mode/padding 的格式
        // if ((mode != null) && (pad != null)) {
        // DES/CBC/PKCS5Padding
        List list = new ArrayList(4);
        list.add(new Transform(alg, "/" + mode + "/" + pad, null, null));
        list.add(new Transform(alg, "/" + mode, null, pad));
        list.add(new Transform(alg, "//" + pad, mode, null));
        list.add(new Transform(alg, "", mode, pad));
        return list;
    }
}
如果transform的格式时“algorithm/mode/padding”,会输出4中形式的Transform,查询支持某种就会返回。

4.ini()

public final void init(int opmode, Key key, SecureRandom random)
        throws InvalidKeyException
{
    initialized = false;
    checkOpmode(opmode);

    if (spi != null) {
        checkCryptoPerm(spi, key);
        spi.engineInit(opmode, key, random);
    } else {
        try {
            chooseProvider(I_KEY, opmode, key, null, null, random);
        } catch (InvalidAlgorithmParameterException e) {
            // should never occur
            throw new InvalidKeyException(e);
        }
    }

    initialized = true;
    this.opmode = opmode;
}
  • 如上代码,执行ini()、update()..等操作时,其实是执行spi.engin**()
  • 如果使用的mod需要传入IV,这使用,init(Mode,Key,IvParameterSpec)出入IV。
  • GCMParameterSpec的时候IV必须每次都不同。

Transform标准名称

可以参考Java Cryptography Architecture Standard Algorithm Name Documentation for JDK 8

总结

看Cipher源码的原因是因为,在看BC的时候看到支持的transform列表中支持的是RSA/OAEP的加密模式,但是JCE中要求的传日格式是“algorithm/mode/padding” or(标准名称)“algorithm”,因此就产生了以问。

BC: Cipher.RSA -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$NoPadding
  aliases: [RSA//RAW, RSA//NOPADDING]
  attributes: {SupportedKeyFormats=PKCS#8|X.509, SupportedKeyClasses=javax.crypto.interfaces.RSAPublicKey|javax.crypto.interfaces.RSAPrivateKey}

BC: Cipher.RSA/RAW -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$NoPadding

BC: Cipher.RSA/PKCS1 -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$PKCS1v1_5Padding
  aliases: [RSA//PKCS1PADDING]

BC: Cipher.RSA/1 -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$PKCS1v1_5Padding_PrivateOnly

BC: Cipher.RSA/2 -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$PKCS1v1_5Padding_PublicOnly

BC: Cipher.RSA/OAEP -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$OAEPPadding
  aliases: [RSA//OAEPPADDING]

BC: Cipher.RSA/ISO9796-1 -> org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi$ISO9796d1Padding
  aliases: [RSA//ISO9796-1PADDING]

可以看出RSA/OAEP的别名正事RSA//OAEPPADING,所以也能够查到RSA//OAEPPADING对应的正是service中的RSA/OAEP。

知其然知其所以然。


X_Shadow
5 声望1 粉丝

小小菜鸟