zoukankan      html  css  js  c++  java
  • JAR包数字签名与验证

    经签名的Jar包内包含了以下内容:

    • 原Jar包内的class文件和资源文件
    • 签名文件 META-INF/*.SF:这是一个文本文件,包含原Jar包内的class文件和资源文件的Hash
    • 签名block文件 META-INF/*.DSA:这是一个数据文件,包含签名者的 certificate 和数字签名。其中 certificate 包含了签名者的有关信息和 public key;数字签名是对 *.SF 文件内的 Hash 值使用 private key 加密得来使用 keytool 和 jarsigner 工具进行 Jar 包签名和验证

    1、使用 keytool 和 jarsigner 工具进行 Jar 包签名和验证

    JDK 提供了 keytool 和 jarsigner 两个工具用来进行 Jar 包签名和验证。

    keytool 用来生成和管理 keystore。keystore 是一个数据文件,存储了 key pair 有关的2种数据:private key 和 certificate,而 certificate 包含了 public key。整个 keystore 用一个密码进行保护,keystore 里面的每一对 key pair 单独用一个密码进行保护。每对 key pair 用一个 alias 进行指定,alias 不区分大小写。

    keytool 支持的算法是:

    • 如果公钥算法为 DSA,则摘要算法使用 SHA-1。这是默认的
    • 如果公钥算法为 RSA,则摘要算法采用 MD5

    jarsigner 读取 keystore,为 Jar 包进行数字签名。jarsigner 也可以对签名的 Jar 包进行验证。

    下面使用 keytool 和 jarsigner 对它进行签名和验证

    第1步:用 keytool 生成 keystore

       打开CMD窗口,键入如下命令生成keystore文件,其中jamesKeyStore 为公钥秘钥数据文件,james 是alias 的 key pair,keypass 的值123456是秘钥指令,storepass 的值123456是秘钥库指令

    keytool -genkey -alias james -keypass 123456  -validity 3650 -keystore jamesKeyStore -storepass 123456

       具体生成过程见下图:

       

    第2步:用 jarsigner 对 Jar 包进行签名

        使用如下命令可以在CMD窗口中验证签名JAR包

    jarsigner -verify cd-vsb-protect-control-1.0-1.jar

    2、JAVA验证JAR包签名

    (1)、JDK对JAR包数字签名验证逻辑

          JDK加载包文件提供了两个类JarFile和JarInputStream,两个类由如下构造方法,参数 boolean verify的作用是限制是否要生成JarVerifier对象,JarVerifier类的功能是提供验证JAR包签名的方法。

    /**
         * Creates a new <code>JarFile</code> to read from the specified
         * <code>File</code> object.
         * @param file the jar file to be opened for reading
         * @param verify whether or not to verify the jar file if
         * it is signed.
         * @throws IOException if an I/O error has occurred
         * @throws SecurityException if access to the file is denied
         *         by the SecurityManager.
         */
        public JarFile(File file, boolean verify) throws IOException {
            this(file, verify, ZipFile.OPEN_READ);
        }
    
    
        /**
         * Creates a new <code>JarFile</code> to read from the specified
         * <code>File</code> object in the specified mode.  The mode argument
         * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
         *
         * @param file the jar file to be opened for reading
         * @param verify whether or not to verify the jar file if
         * it is signed.
         * @param mode the mode in which the file is to be opened
         * @throws IOException if an I/O error has occurred
         * @throws IllegalArgumentException
         *         if the <tt>mode</tt> argument is invalid
         * @throws SecurityException if access to the file is denied
         *         by the SecurityManager
         * @since 1.3
         */
        public JarFile(File file, boolean verify, int mode) throws IOException {
            super(file, mode);
            this.verify = verify;
        } 
    /**
         * Creates a new <code>JarInputStream</code> and reads the optional
         * manifest. If a manifest is present and verify is true, also attempts
         * to verify the signatures if the JarInputStream is signed.
         *
         * @param in the actual input stream
         * @param verify whether or not to verify the JarInputStream if
         * it is signed.
         * @exception IOException if an I/O error has occurred
         */
        public JarInputStream(InputStream in, boolean verify) throws IOException {
            super(in);
            this.doVerify = verify;
    
            // This implementation assumes the META-INF/MANIFEST.MF entry
            // should be either the first or the second entry (when preceded
            // by the dir META-INF/). It skips the META-INF/ and then
            // "consumes" the MANIFEST.MF to initialize the Manifest object.
            JarEntry e = (JarEntry)super.getNextEntry();
            if (e != null && e.getName().equalsIgnoreCase("META-INF/"))
                e = (JarEntry)super.getNextEntry();
            first = checkManifest(e);
        }
    
        private JarEntry checkManifest(JarEntry e)
            throws IOException
        {
            if (e != null && JarFile.MANIFEST_NAME.equalsIgnoreCase(e.getName())) {
                man = new Manifest();
                byte bytes[] = getBytes(new BufferedInputStream(this));
                man.read(new ByteArrayInputStream(bytes));
                closeEntry();
                if (doVerify) {
                    jv = new JarVerifier(bytes);
                    mev = new ManifestEntryVerifier(man);
                }
                return (JarEntry)super.getNextEntry();
            }
            return e;
        }

    如下代码所示在JarInputStream类对象调用getNextEntry方法获取JarEntry对象时,如果jv对象不为空时,要调用JarVerifier类的beginEntry方法,而此方法最总调用了ManifestEntryVerifier类的mev.setEntry(null, je)方法,

    ManifestEntryVerifier类用来做JAR安全证书验证。

    public ZipEntry getNextEntry() throws IOException {
            JarEntry e;
            if (first == null) {
                e = (JarEntry)super.getNextEntry();
                if (tryManifest) {
                    e = checkManifest(e);
                    tryManifest = false;
                }
            } else {
                e = first;
                if (first.getName().equalsIgnoreCase(JarIndex.INDEX_NAME))
                    tryManifest = true;
                first = null;
            }
            if (jv != null && e != null) {
                // At this point, we might have parsed all the meta-inf
                // entries and have nothing to verify. If we have
                // nothing to verify, get rid of the JarVerifier object.
                if (jv.nothingToVerify() == true) {
                    jv = null;
                    mev = null;
                } else {
                    jv.beginEntry(e, mev);
                }
            }
            return e;
        }
     /**
         * This method scans to see which entry we're parsing and
         * keeps various state information depending on what type of
         * file is being parsed.
         */
        public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
            throws IOException
        {
            if (je == null)
                return;
    
            if (debug != null) {
                debug.println("beginEntry "+je.getName());
            }
    
            String name = je.getName();
    
            /*
             * Assumptions:
             * 1. The manifest should be the first entry in the META-INF directory.
             * 2. The .SF/.DSA/.EC files follow the manifest, before any normal entries
             * 3. Any of the following will throw a SecurityException:
             *    a. digest mismatch between a manifest section and
             *       the SF section.
             *    b. digest mismatch between the actual jar entry and the manifest
             */
    
            if (parsingMeta) {
                String uname = name.toUpperCase(Locale.ENGLISH);
                if ((uname.startsWith("META-INF/") ||
                     uname.startsWith("/META-INF/"))) {
    
                    if (je.isDirectory()) {
                        mev.setEntry(null, je);
                        return;
                    }
    
                    if (uname.equals(JarFile.MANIFEST_NAME) ||
                            uname.equals(JarIndex.INDEX_NAME)) {
                        return;
                    }
    
                    if (SignatureFileVerifier.isBlockOrSF(uname)) {
                        /* We parse only DSA, RSA or EC PKCS7 blocks. */
                        parsingBlockOrSF = true;
                        baos.reset();
                        mev.setEntry(null, je);
                        return;
                    }
    
                    // If a META-INF entry is not MF or block or SF, they should
                    // be normal entries. According to 2 above, no more block or
                    // SF will appear. Let's doneWithMeta.
                }
            }
    
            if (parsingMeta) {
                doneWithMeta();
            }
    
            if (je.isDirectory()) {
                mev.setEntry(null, je);
                return;
            }
    
            // be liberal in what you accept. If the name starts with ./, remove
            // it as we internally canonicalize it with out the ./.
            if (name.startsWith("./"))
                name = name.substring(2);
    
            // be liberal in what you accept. If the name starts with /, remove
            // it as we internally canonicalize it with out the /.
            if (name.startsWith("/"))
                name = name.substring(1);
    
            // only set the jev object for entries that have a signature
            // (either verified or not)
            if (!name.equals(JarFile.MANIFEST_NAME)) {
                if (sigFileSigners.get(name) != null ||
                        verifiedSigners.get(name) != null) {
                    mev.setEntry(name, je);
                    return;
                }
            }
    
            // don't compute the digest for this entry
            mev.setEntry(null, je);
    
            return;
        }

    (2)、使用java验证JAR包签名

    看了上面JDK提供的JAR相关的工具类,我们可以使用JarInputStream类的逻辑来验证,思想是通过空读取JarEntry对象验证包文件中的每个文件数字签名是否被篡改,在获取JarInputStream类对象时设置verify参数值为true,当声明需要做签名验证时在使用jarIn.getNextJarEntry()获取JarEntry对象如果文件被篡改会跑出异常java.lang.SecurityException: SHA-256 digest error for 文件名,这个时候表明JAR签名验证不通过。

    ,代码实现如下:

    public static void verify(String path) throws IOException{
            File file = new File(path);
            InputStream in = new FileInputStream(file);
            JarInputStream jarIn = new JarInputStream(in,true);
            while(jarIn.getNextJarEntry() != null){
                continue;
            }
        }

      

  • 相关阅读:
    设计模式详细系列教程 (三)以网上购物通用的订单流程 详解状态模式
    DIV +CSS 系列详细教程 (一)初识
    JAVA JDK环境变量的配置
    SCM软件配置管理 (二) SVN管理平台搭建以及详细设置
    设计模式详细系列教程 (四) 无处不在的单例模式
    Java、JavaScript、asp.net 、jquery 详细分析
    牛腩新闻发布系统 (6) 在线浮动的客服功能
    牛腩新闻发布系统 (4)验证码的生成
    UML系列 (六)如何提取用例技术?
    牛腩新闻发布系统 (5) 总结
  • 原文地址:https://www.cnblogs.com/cymiao/p/8398979.html
Copyright © 2011-2022 走看看