美文网首页
JDK加解密Cipher类造成的OOM

JDK加解密Cipher类造成的OOM

作者: 木易三石桑 | 来源:发表于2018-08-22 10:21 被阅读0次

背景

本司渠道服务上线后每运行一周左右,内存呈缓慢上升趋势,并最终引起OOM

问题描述

线上渠道服务,是负责整个对外的渠道接入,有段时间突然发现服务会自动宕机,经过重启又稳定运行,但慢慢经过一周左右的时间又自动宕机,后经过grafana监控到系统指标中Memory useage指标趋势是呈缓慢上升趋势,最终造成系统OOM

问题排查过程

线上通过jmap 抓到dump文件,经过排查发现 加解密提供者 BouncyCastleProvider 这个对象占用了大量内存并未释放的趋势,后逐渐通过代码排查发现
BouncyCastleProvider 的生成 是因为调用了Cipher.getInstance 方法

关键代码片段

/**
     * 3DES加密
     *
     * @param value     普通文本
     * @param secretKey
     * @return
     * @throws Exception
     */
    public static String encrypt3DES(String value, String secretKey) throws Exception {
        String plainText = null;
        byte[] keyBytes = newByte(24);
        to24Key(secretKey, keyBytes);
        KeySpec dks = new DESedeKeySpec(keyBytes);
        SecretKey secKey = SecretKeyFactory.getInstance(Algorithm).generateSecret(dks);
        Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, secKey);
        byte[] srcBytes = value.getBytes(encoding);
        int srcLen = srcBytes.length;
        int valuelen = srcLen + 1;
        int encLen = ((valuelen % 8) == 0) ? valuelen : ((valuelen / 8 + 1) * 8);
        byte[] encBytes = newByte(encLen);
        encBytes[0] = (byte) srcLen;
        System.arraycopy(srcBytes, 0, encBytes, 1, srcLen);
        // 正式执行解密操作
        byte[] encryptBytes = cipher.doFinal(encBytes);
        plainText = Base64.encode(encryptBytes);
        return plainText;
    }

由上代码可知,加解密的主要对象Cipher是这么实例化的

Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");

继续跟进getInstance方法内部

public static final Cipher getInstance(String var0) throws NoSuchAlgorithmException, NoSuchPaddingException {
        List var1 = getTransforms(var0);
        ArrayList var2 = new ArrayList(var1.size());
        Iterator var3 = var1.iterator();

        while(var3.hasNext()) {
            Cipher.Transform var4 = (Cipher.Transform)var3.next();
            var2.add(new ServiceId("Cipher", var4.transform));
        }

        List var11 = GetInstance.getServices(var2);
        Iterator var12 = var11.iterator();
        Exception var5 = null;

        while(true) {
            Service var6;
            Cipher.Transform var7;
            int var8;
            do {
                do {
                    do {
                        if (!var12.hasNext()) {
                            throw new NoSuchAlgorithmException("Cannot find any provider supporting " + var0, var5);
                        }

                        var6 = (Service)var12.next();
                    } while(!JceSecurity.canUseProvider(var6.getProvider()));

                    var7 = getTransform(var6, var1);
                } while(var7 == null);

                var8 = var7.supportsModePadding(var6);
            } while(var8 == 0);

            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;
            }
        }
    }

由上代码可发现 最终会走到 JceSecurity.canUseProvider(var6.getProvider()) 方法中,此var6.getProvider() 是每次调用该getInstance方法的时候,每次都会生成的,根据此线索继续往下跟进canUseProvider方法内部

static synchronized Exception getVerificationResult(Provider var0) {
        Object var1 = verificationResults.get(var0);
        if (var1 == PROVIDER_VERIFIED) {
            return null;
        } else if (var1 != null) {
            return (Exception)var1;
        } else if (verifyingProviders.get(var0) != null) {
            return new NoSuchProviderException("Recursion during verification");
        } else {
            Exception var3;
            try {
                verifyingProviders.put(var0, Boolean.FALSE);
                URL var2 = getCodeBase(var0.getClass());
                verifyProviderJar(var2);
                verificationResults.put(var0, PROVIDER_VERIFIED);
                var3 = null;
                return var3;
            } catch (Exception var7) {
                verificationResults.put(var0, var7);
                var3 = var7;
            } finally {
                verifyingProviders.remove(var0);
            }

            return var3;
        }
    }

此段代码的核心是 verifyingProviders.put(var0, Boolean.FALSE); var0,是外部传进来的var6.getProvider(),之前分析过var6.getProvider()是每次调用都会重新生成,那么这里每次会将重新生成的Provider插入到verifyingProviders中;(马上要破案了)

 private static final Map<Provider, Object> verifyingProviders = new IdentityHashMap();
 

我们可以看到verifyingProviders是一个静态的IdentityHashMap,这个Map在存储类的时候并不是使用类的equals方法来判断是否Key已经存在,而是使用 == 来判断是否Key已经存在的;换句话说就是当两个对象不 == 那么此Map就会将这个对象存进去,由于静态对象的关系会造成系统内存不会释放,从而导致程序OOM;

解决方案

通过查看其源码,发现 Cipher.getInstance 方法有一个重载方法,Cipher getInstance(String var0, Provider var1),此方法由外部传入一个Provider,这样外部只要保证此Provider是全局唯一即可;

后续改造方案

static Provider provider = new BouncyCastleProvider();
    /**
     * 3DES加密
     *
     * @param value     普通文本
     * @param secretKey
     * @return
     * @throws Exception
     */
    public static String encrypt3DES(String value, String secretKey) throws Exception {
        xxxxxx
        Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding", provider);
        xxxxxx
    }

相关文章

  • JDK加解密Cipher类造成的OOM

    背景 本司渠道服务上线后每运行一周左右,内存呈缓慢上升趋势,并最终引起OOM 问题描述 线上渠道服务,是负责整个对...

  • 使用AES对文件加解密 java实现

    对文件进行AES加解密的过程,使用Java语言调用Cipher库实现。本文实现的加解密方式为AES CBC方式,使...

  • iOS-OOM

    什么是OOM?OOM一定会kill吗?怎么监控识别OOM?监控到了OOM怎么分析是谁造成的? 1、崩溃和OOM A...

  • Golang-AES加密(CBC模式,PKCS7填充)

    对称加密算法,即加密和解密使用一样的密钥的加解密算法。分组密码(block cipher),是每次只能处理特定长度...

  • 对称加密算法和分组密码的模式

    对称加密算法,即加密和解密使用一样的密钥的加解密算法。分组密码(block cipher),是每次只能处理特定长度...

  • JCA 实践记录——Cipher

    Cipher类提供加密和解密的功能。 实例化 Cipher没有公开的构造方法,所以只能调用其静态方法getInst...

  • Android应用性能优化和性能分析

    Memory 分析和优化 有OOM发生但是没有现场保留 结合logcat和代码分析造成oom的泄漏点, 一般OOM...

  • Rc4的加密和解密

    Rc4: 在密码学中,RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使...

  • Python3.7实现RC4加密解密(超详细)

    在密码学中,RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密...

  • 常用的工具方法

    日期类 附件类 Json类 加解密 Http调用 Xml转换

网友评论

      本文标题:JDK加解密Cipher类造成的OOM

      本文链接:https://www.haomeiwen.com/subject/pqeeiftx.html