java 安全体系算法调用过程

java 安全体系主要分为

  • JCA(Java Cryptography Architectrue)java加密体系
  • JCE(Java Cryptography Extension)java加密拓展
  • JSSE (Java Secure Socket Extesion)java套接字安全拓展
  • JAAS(Java Authentication and Authentication Service)Java验证和授权API

它们并不执行各种算法,只是连接应用和实际算法实现程序的一组接口。软件开发商根据JCE接口,将各种算法实现后,打包成一个Provider,可以动态地加到Java运行环境中

Android Provider 当中默认包含的算法类型

        // JCA
        addEngine("AlgorithmParameterGenerator",        false, null);
        addEngine("AlgorithmParameters",                false, null);
        addEngine("KeyFactory",                         false, null);
        addEngine("KeyPairGenerator",                   false, null);
        addEngine("KeyStore",                           false, null);
        addEngine("MessageDigest",                      false, null);
        addEngine("SecureRandom",                       false, null);
        addEngine("Signature",                          true,  null);
        addEngine("CertificateFactory",                 false, null);
        addEngine("CertPathBuilder",                    false, null);
        addEngine("CertPathValidator",                  false, null);
        addEngine("CertStore",                          false,
                            "java.security.cert.CertStoreParameters");
        // JCE
        addEngine("Cipher",                             true,  null);
        addEngine("ExemptionMechanism",                 false, null);
        addEngine("Mac",                                true,  null);
        addEngine("KeyAgreement",                       true,  null);
        addEngine("KeyGenerator",                       false, null);
        addEngine("SecretKeyFactory",                   false, null);
        // JSSE
        addEngine("KeyManagerFactory",                  false, null);
        addEngine("SSLContext",                         false, null);
        addEngine("TrustManagerFactory",                false, null);
        // JGSS
        addEngine("GssApiMechanism",                    false, null);
        // SASL
        addEngine("SaslClientFactory",                  false, null);
        addEngine("SaslServerFactory",                  false, null);
        // POLICY
        addEngine("Policy",                             false,
                            "java.security.Policy$Parameters");
        // CONFIGURATION
        addEngine("Configuration",                      false,
                            "javax.security.auth.login.Configuration$Parameters");
        // XML DSig
        addEngine("XMLSignatureFactory",                false, null);
        addEngine("KeyInfoFactory",                     false, null);
        addEngine("TransformService",                   false, null);
        // Smart Card I/O
        addEngine("TerminalFactory",                    false,
                            "java.lang.Object");

Java 安全体系扩展性很强,除了 jdk 提供的算法实现外 可以使用第三方java拓展,常用安全供应者的有

  1. Bouncy Castle
  2. Commons Codec – Apache

如何配置添加第三方的安全供应者

方法 1 .配置文件

Mac 桌面系统配置扩展 provider ,并没有加入 Bouncy Castle

配置文件在 jdk安装目录的 jre/lib/security/java.security


java 安全体系算法调用过程_第1张图片
1.png

Android 系统配置在 security.properties


java 安全体系算法调用过程_第2张图片
2.png

Android 默认配置了 BouncyCastleProvider ,但是阉割版的,BouncyCastleProvider 类中有的,但是 Android 上不一定会有.
java 安全体系算法调用过程_第3张图片
3.png
  1. 代码动态添加
Security.addProvider(new BouncyCastleProvider());

(Android 手机可能不会生效,因为 Android 已经默认添加过阉割版本的 可以先移除系统自带的再添加,不过部分手机仍然不会生效);

最稳妥的办法就是 当使用到某种算法的时候就直接传入一个 Provider 对象,就会使用到我们扩展的安全供应者的算法

Signature signature = Signature.getInstance("SM3withSM2", new BouncyCastleProvider());

Android 默认配置的 Provider 初始化过程

在 java.security.Security 类当中有一段静态代码块去加载 security.properties 文件,就会把security.properties 的配置的属性加载进来到 props 对象

   static {
        props = new Properties();
        InputStream is = null;
        InputStream propStream = Security.class.getResourceAsStream("security.properties");
         is  = new BufferedInputStream(propStream);
         props.load(is);
    }

在 java.security.Provider.Providers类中的静态代码块中

 static {
        // set providerList to empty list first in case initialization somehow
        // triggers a getInstance() call (although that should not happen)
        providerList = ProviderList.EMPTY;
        // 1. 新建一个 ProviderList 对象
        providerList = ProviderList.fromSecurityProperties(); 

        // removeInvalid is specified to try initializing all configured providers
        // and removing those that aren't instantiable. This has the side effect
        // of eagerly initializing all providers.
        final int numConfiguredProviders = providerList.size();
        // 2.初始化并移除那些无法初始化的各个 Provider 
        providerList = providerList.removeInvalid();
        if (numConfiguredProviders != providerList.size()) {
            throw new AssertionError("Unable to configure default providers");
        }
    }

fromSecurityProperties 方法中会 new ProviderList();对象

在 sun.security.jca.ProviderList
读取 props 对象保存的security.properties
中security.provider 获取类名和参数构建 ProviderConfig对象

  /**
     * Return a new ProviderList parsed from the java.security Properties.
     */
    private ProviderList() {
        List configList = new ArrayList<>();
        for (int i = 1; true; i++) {
            String entry = Security.getProperty("security.provider." + i);
            if (entry == null) {
                break;
            }
            entry = entry.trim();
            if (entry.length() == 0) {
                System.err.println("invalid entry for " +
                                   "security.provider." + i);
                break;
            }
            int k = entry.indexOf(' ');
            ProviderConfig config;
            if (k == -1) {
                config = new ProviderConfig(entry);
            } else {
                String className = entry.substring(0, k);
                String argument = entry.substring(k + 1).trim();
                config = new ProviderConfig(className, argument);
            }

            // Get rid of duplicate providers.
            if (configList.contains(config) == false) {
                configList.add(config);
            }
        }
    }

这个就拿到了配置的 provider 的类名 构建 ProviderConfig对象 并且存储在 ProviderConfig[] configs;数组当中

    /**
     * Try to load all Providers and return the ProviderList. If one or
     * more Providers could not be loaded, a new ProviderList with those
     * entries removed is returned. Otherwise, the method returns this.
     */
    ProviderList removeInvalid() {
        int n = loadAll();
        if (n == configs.length) {
            return this;
        }
        ProviderConfig[] newConfigs = new ProviderConfig[n];
        for (int i = 0, j = 0; i < configs.length; i++) {
            ProviderConfig config = configs[i];
            if (config.isLoaded()) {
                newConfigs[j++] = config;
            }
        }
        return new ProviderList(newConfigs, true);
    }

调用 loadAll 方法

    // attempt to load all Providers not already loaded
    private int loadAll() {
        int n = 0;
        for (int i = 0; i < configs.length; i++) {
            Provider p = configs[i].getProvider();
            if (p != null) {
                n++;
            }
        }
        if (n == configs.length) {
            allLoaded = true;
        }
        return n;
    }

调用各个 ProviderConfig对象的 getProvider方法

    /**
     * Get the provider object. Loads the provider if it is not already loaded.
     */
    synchronized Provider getProvider() {
        // volatile variable load
        Provider p = provider;
        if (p != null) {
            return p;
        }
        if (shouldLoad() == false) {
            return null;
        }
        if (isLoading) {
            return null;
        }
        try {
            isLoading = true;
            tries++;
            p = doLoadProvider();
        } finally {
            isLoading = false;
        }
        provider = p;
        return p;
    }

调用 doLoadProvider 方法

    private Provider doLoadProvider() {
         return initProvider(className, Object.class.getClassLoader());
    }

调用 initProvider 通过反射调用构造函数获取 Provider 对象

    private Provider initProvider(String className, ClassLoader cl) throws Exception {
        Class provClass;
        if (cl != null) {
            provClass = cl.loadClass(className);
        } else {
            provClass = Class.forName(className);
        }
        Object obj;
        if (hasArgument() == false) {
            obj = provClass.newInstance();
        } else {
            Constructor cons = provClass.getConstructor(CL_STRING);
            obj = cons.newInstance(argument);
        }
        if (obj instanceof Provider) {
            return (Provider)obj;
        } else {
            disableLoad();
            return null;
        }
    }

然后 配置的 Provider 类就会加载进来并且创建对象.现在已将 BouncyCastleProvider 配置进来了

举例说明 MD5 算法是如何被加载进来被使用的 ,其他的也类似.

BouncyCastleProvider 的构造函数 调用父类的构造函数 PROVIDER_NAME ,这个是一个 key ,每个 Provider 通过这个参数也能够找到相应的 Provider 对象 .

public static final String PROVIDER_NAME = "BC";
  /**
     * Construct a new provider.  This should only be required when
     * using runtime registration of the provider using the
     * Security.addProvider() mechanism.
     */
    public BouncyCastleProvider()
    {
        super(PROVIDER_NAME, 1.56, info);
         setup();
    }

然后调用 setup方法

    /*
     * Configurable digests
     */
    private static final String DIGEST_PACKAGE = "org.bouncycastle.jcajce.provider.digest.";
    private static final String[] DIGESTS =
    {
        // BEGIN android-removed
        // "GOST3411", "Keccak", "MD2", "MD4", "MD5", "SHA1", "RIPEMD128", "RIPEMD160", "RIPEMD256", "RIPEMD320", "SHA224",
        // "SHA256", "SHA384", "SHA512", "SHA3", "Skein", "SM3", "Tiger", "Whirlpool", "Blake2b"
        // END android-removed
        // BEGIN android-added
        "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512",
        // END android-added
    };
    
    private void setup()
    {
        loadAlgorithms(DIGEST_PACKAGE, DIGESTS);
    }

调用 loadAlgorithms ,通过 for 循环加载反射拿到相应的类对象 如 MD5算法
就会加载 org.bouncycastle.jcajce.provider.digest.MD5$Mappings 这个类(org.bouncycastle.jcajce.provider.digest.MD5的静态内部类)
并且创建对象调用 configure方法

    private void loadAlgorithms(String packageName, String[] names)
    {
        for (int i = 0; i != names.length; i++)
        {
            Class clazz = null;
            try
            {
                ClassLoader loader = this.getClass().getClassLoader();

                if (loader != null)
                {
                    clazz = loader.loadClass(packageName + names[i] + "$Mappings");
                }
                else
                {
                    clazz = Class.forName(packageName + names[i] + "$Mappings");
                }
            }
            catch (ClassNotFoundException e)
            {
                // ignore
            }

            if (clazz != null)
            {
                try
                {
                    ((AlgorithmProvider)clazz.newInstance()).configure(this);
                }
                catch (Exception e)
                {   // this should never ever happen!!
                    throw new InternalError("cannot create instance of "
                        + packageName + names[i] + "$Mappings : " + e);
                }
            }
        }
    }

在 org.bouncycastle.jcajce.provider.digest.MD5类中

package org.bouncycastle.jcajce.provider.digest;

public class MD5
{
    private MD5()
    {
    }
    /**
     * MD5 HashMac
     */
    public static class HashMac
        extends BaseMac
    {
        public HashMac()
        {
            super(new HMac(new MD5Digest()));
        }
    }

    public static class KeyGenerator
        extends BaseKeyGenerator
    {
        public KeyGenerator()
        {
            super("HMACMD5", 128, new CipherKeyGenerator());
        }
    }

    static public class Digest
        extends BCMessageDigest
        implements Cloneable
    {
        public Digest()
        {
            super(new MD5Digest());
        }

        public Object clone()
            throws CloneNotSupportedException
        {
            Digest d = (Digest)super.clone();
            d.digest = new MD5Digest((MD5Digest)digest);

            return d;
        }
    }

    public static class Mappings
        extends DigestAlgorithmProvider
    {
        private static final String PREFIX = MD5.class.getName();

        public Mappings()
        {
        }

        public void configure(ConfigurableProvider provider)
        {
            provider.addAlgorithm("MessageDigest.MD5", PREFIX + "$Digest");
            provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md5, "MD5");

            addHMACAlgorithm(provider, "MD5", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
            addHMACAlias(provider, "MD5", IANAObjectIdentifiers.hmacMD5);
        }
    }
}

configure 方法调用 BouncyCastleProvider的 addAlgorithm

provider.addAlgorithm("MessageDigest.MD5", PREFIX + "$Digest");
provider.addAlgorithm("Alg.Alias.MessageDigest." + PKCSObjectIdentifiers.md5, "MD5");

PKCSObjectIdentifiers.md5 == "1.2.840.113549.2.5"

BouncyCastleProvider的 addAlgorithm调用了 put方法 ,put 是父类 Provider的方法

    public void addAlgorithm(String key, String value)
    {
        if (containsKey(key))
        {
            throw new IllegalStateException("duplicate provider key (" + key + ") found");
        }

        put(key, value);
    }

Provider的put 方法调用 implPut

    @Override
    public synchronized Object put(Object key, Object value) {
        check("putProviderProperty."+name);
        return implPut(key, value);
    }

implPut 就会把
字符串 key "MessageDigest.MD5",和 value 类名 org.bouncycastle.jcajce.provider.digest.MD5$Digest

key "Alg.Alias.MessageDigest.1.2.840.113549.2.5" 和 value "MD5"

存入legacyStrings Map 当中

    private transient Map legacyStrings;
    
    private Object implPut(Object key, Object value) {
        if ((key instanceof String) && (value instanceof String)) {
            if (!checkLegacy(key)) {
                return null;
            }
            legacyStrings.put((String)key, (String)value);
        }
        return super.put(key, value);
    }

如何使用 BouncyCastleProvider 的 MD5 方法

MessageDigest md = MessageDigest.getInstance("MD5",new BouncyCastleProvider());

MessageDigest .getInstance 参数分别为 ("MD5",BouncyCastleProvider对象)

    public static MessageDigest getInstance(String algorithm,
                                            Provider provider)
        throws NoSuchAlgorithmException
    {
        if (provider == null)
            throw new IllegalArgumentException("missing provider");
        Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
        if (objs[0] instanceof MessageDigest) {
            MessageDigest md = (MessageDigest)objs[0];
            md.provider = (Provider)objs[1];
            return md;
        } else {
            MessageDigest delegate =
                new Delegate((MessageDigestSpi)objs[0], algorithm);
            delegate.provider = (Provider)objs[1];
            return delegate;
        }
    }

调用 Security 的 getImpl ("MD5","MessageDigest",BouncyCastleProvider对象)

    static Object[] getImpl(String algorithm, String type, Provider provider)
            throws NoSuchAlgorithmException {
        return GetInstance.getInstance
            (type, getSpiClass(type), algorithm, provider).toArray();
    }

调用GetInstance类的 getSpiClass 方法

加载 java.security.MessageDigestSpi 类并把它加入到 spiMap 缓存起来方便下次使用

    private static Class getSpiClass(String type) {
        Class clazz = spiMap.get(type);
        if (clazz != null) {
            return clazz;
        }
        try {
            clazz = Class.forName("java.security." + type + "Spi");
            spiMap.put(type, clazz);
            return clazz;
        } catch (ClassNotFoundException e) {
            throw new AssertionError("Spi class not found", e);
        }
    }

GetInstance类 getInstance ("MessageDigest",MessageDigestSpi.class,"MD5",BouncyCastleProvider对象)

    public static Instance getInstance(String type, Class clazz,
            String algorithm, Provider provider)
            throws NoSuchAlgorithmException {
        return getInstance(getService(type, algorithm, provider), clazz);
    }

GetInstance类 getService ("MessageDigest","MD5",BouncyCastleProvider对象)

    public static Service getService(String type, String algorithm,
            Provider provider) throws NoSuchAlgorithmException {
        Service s = provider.getService(type, algorithm);
        return s;
    }

调用 BouncyCastleProvider 父类的Provider getService ("MessageDigest","MD5")

    public synchronized Service getService(String type, String algorithm) {

        ServiceKey key = previousKey;
        if (key.matches(type, algorithm) == false) {
            key = new ServiceKey(type, algorithm, false);
            previousKey = key;
        }
        ensureLegacyParsed();
        return (legacyMap != null) ? legacyMap.get(key) : null;
    }

调用 Provider 的ensureLegacyParsed 方法

在之前分析 BouncyCastleProvider 对象在初始化的过程中已经把字符串
key "MessageDigest.MD5",和 value 类名 org.bouncycastle.jcajce.provider.digest.MD5$Digest
key "Alg.Alias.MessageDigest.1.2.840.113549.2.5" 和 value "MD5"
存入legacyStrings 的Map 当中 ,在 ensureLegacyParsed方法 for 循环取出调用 parseLegacyPut

    private void ensureLegacyParsed() {
        for (Map.Entry entry : legacyStrings.entrySet()) {
            parseLegacyPut(entry.getKey(), entry.getValue());
        }
        removeInvalidServices(legacyMap);
        legacyChanged = false;
    }

调用 Provider 的 parseLegacyPut ("MessageDigest.MD5","org.bouncycastle.jcajce.provider.digest.MD5$Digest");方法 解析 生成key 为 ServiceKey对象,Value 为 Provider.Service,放入 legacyMap当中

ServiceKey
    .type ="MessageDigest"
    .originalAlgorithm = "MD5"
    .algorithm = "MD5"

Provider.Service 
    .provider 属性为BouncyCastleProvider对象
    .type = "MessageDigest";
    .algorithm = "MD5"
    .className = "org.bouncycastle.jcajce.provider.digest.MD5$Digest"

并为 Provider.Service 添加 aliases; 1.2.840.113549.2.5,

    private transient Map legacyMap;
    
    private final static String ALIAS_PREFIX = "Alg.Alias.";
    private final static String ALIAS_PREFIX_LOWER = "alg.alias.";
    private final static int ALIAS_LENGTH = ALIAS_PREFIX.length();

    private void parseLegacyPut(String name, String value) {
        if (name.toLowerCase(ENGLISH).startsWith(ALIAS_PREFIX_LOWER)) {
            // e.g. put("Alg.Alias.MessageDigest.SHA", "SHA-1");
            // aliasKey ~ MessageDigest.SHA
            String stdAlg = value;
            String aliasKey = name.substring(ALIAS_LENGTH);
            String[] typeAndAlg = getTypeAndAlgorithm(aliasKey);
            if (typeAndAlg == null) {
                return;
            }
            String type = getEngineName(typeAndAlg[0]);
            String aliasAlg = typeAndAlg[1].intern();
            ServiceKey key = new ServiceKey(type, stdAlg, true);
            Service s = legacyMap.get(key);
            if (s == null) {
                s = new Service(this);
                s.type = type;
                s.algorithm = stdAlg;
                legacyMap.put(key, s);
            }
            legacyMap.put(new ServiceKey(type, aliasAlg, true), s);
            s.addAlias(aliasAlg);
        } else {
            String[] typeAndAlg = getTypeAndAlgorithm(name);
            if (typeAndAlg == null) {
                return;
            }
            int i = typeAndAlg[1].indexOf(' ');
            if (i == -1) {
                // e.g. put("MessageDigest.SHA-1", "sun.security.provider.SHA");
                String type = getEngineName(typeAndAlg[0]);
                String stdAlg = typeAndAlg[1].intern();
                String className = value;
                ServiceKey key = new ServiceKey(type, stdAlg, true);
                Service s = legacyMap.get(key);
                if (s == null) {
                    s = new Service(this);
                    s.type = type;
                    s.algorithm = stdAlg;
                    legacyMap.put(key, s);
                }
                s.className = className;
            } else { // attribute
                // e.g. put("MessageDigest.SHA-1 ImplementedIn", "Software");
                String attributeValue = value;
                String type = getEngineName(typeAndAlg[0]);
                String attributeString = typeAndAlg[1];
                String stdAlg = attributeString.substring(0, i).intern();
                String attributeName = attributeString.substring(i + 1);
                // kill additional spaces
                while (attributeName.startsWith(" ")) {
                    attributeName = attributeName.substring(1);
                }
                attributeName = attributeName.intern();
                ServiceKey key = new ServiceKey(type, stdAlg, true);
                Service s = legacyMap.get(key);
                if (s == null) {
                    s = new Service(this);
                    s.type = type;
                    s.algorithm = stdAlg;
                    legacyMap.put(key, s);
                }
                s.addAttribute(attributeName, attributeValue);
            }
        }
    }

GetInstance类 getInstance (Provider.Service 对象,MessageDigestSpi.class)

    public static Instance getInstance(Service s, Class clazz)
            throws NoSuchAlgorithmException {
        Object instance = s.newInstance(null);
        checkSuperClass(s, instance.getClass(), clazz);
        return new Instance(s.getProvider(), instance);
    }

调用 Provider.Service newInstance方法,会加载 org.bouncycastle.jcajce.provider.digest.MD5$Digest 类并反射生成对象赋值给 instance

Provider.Service 
    .provider 属性为BouncyCastleProvider对象
    .type = "MessageDigest";
    .algorithm = "MD5"
    .className = "org.bouncycastle.jcajce.provider.digest.MD5$Digest"

然后调用 GetInstance 静态内部类GetInstance.Instance 的构造函数(BouncyCastleProvider对象,instance)

    /**
     * Static inner class representing a newly created instance.
     */
    public static final class Instance {
        // public final fields, access directly without accessors
        public final Provider provider;
        public final Object impl;
        private Instance(Provider provider, Object impl) {
            this.provider = provider;
            this.impl = impl;
        }
        // Return Provider and implementation as an array as used in the
        // old Security.getImpl() methods.
        public Object[] toArray() {
            return new Object[] {impl, provider};
        }
    }

返回的 toArray 返回一个数组
最后
MessageDigest md = MessageDigest.getInstance("MD5",new BouncyCastleProvider());
中返回的就是 MessageDigest md = (MessageDigest)objs[0];
objs[0]就是 org.bouncycastle.jcajce.provider.digest.MD5$Digest 对象.以后使用这个对象执行 MD5 的算法

这样我们就通过添加 Provider 扩展了 Java 体系的算法

经过以上的分析,我们就可以自己自定义 Provider 扩展的Java 的安全体系的算法

我们并不真的去实现 MD5 算法,只是测试我们的 Provider 是否添加成功,自己写的MD5算法是否被调用

自定义 Provider 扩展的Java 的安全体系的算法

  1. 创建一个类继承 Provider ,调用父类的构造函数
public class MyProvider extends Provider {
    /**
     * Constructs a provider with the specified name, version number,
     * and information.
     *
     * @param name    the provider name.
     * @param version the provider version number.
     * @param info    a description of the provider and its services.
     */
    protected MyProvider(String name, double version, String info) {
        super(name, version, info);
    }

    public static final String PROVIDER_NAME = "com.example.MyProvider";

    MyProvider(){
        this(PROVIDER_NAME,1.4,"com.example.MyProvider info_version v1.4");
        init();
    }

    private void init() {
        put("MessageDigest.MD5","com.example.MyMessageDigest5");
    }
}
  1. 创建 com.example.MyMessageDigest5 类 这个类必须是 MessageDigestSpi的实现类

MessageDigest 继承了 MessageDigestSpi ,最后 engineDigest()方法就是 md5 的计算结果

public class MyMessageDigest5 extends MessageDigest {

    public MyMessageDigest5() {
        super("MD5");
    }

    protected void engineUpdate(byte input) {

    }

    protected void engineUpdate(byte[] input, int offset, int len) {

    }

    protected byte[] engineDigest() {
        return "MyMessageDigest5 hello".getBytes();
    }

    protected void engineReset() {

    }
}
  1. 使用 MyProvider ,有两种使用方式一种是直接传入Provider对象,一种是用 Security.addProvider 接口添加再用 Provider的名字访问
    private static String src = "hello provider";

    public static void main(String argv[]) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5", new MyProvider());
            byte[] md5Bytes = md.digest(src.getBytes());
            System.out.println(new String(md5Bytes));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        
        try {

            Security.addProvider(new MyProvider());
            MessageDigest md = MessageDigest.getInstance("MD5", MyProvider.PROVIDER_NAME);
            byte[] md5Bytes = md.digest(src.getBytes());
            System.out.println(new String(md5Bytes));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        }

    }

最后的输出结果,证明我们的 Provider 已添加,MD5算法 已被调用

java 安全体系算法调用过程_第4张图片
ProviderTest.png

你可能感兴趣的:(java 安全体系算法调用过程)