经签名的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;
}
}

JAR包数字签名与验证的更多相关文章

  1. 给jar包进行数字签名(2014-06-28记)

    整理一下两年前用到的一些资料. 为了使Applet或者Java Web Start程序能够访问客户端本地资源,需要对Applet或者JWS程序jar包进行数据签名,当客户端打开Applet或者JWS程 ...

  2. 写好的mapreduce程序,编译,打包,得到最后的jar包! 验证jar包 ! 整体流程

    创建一个bin目录,用于存放编译.java文件产生的.class等结果,然后编译! 编译结果! 打包操作! 打包结果! 验证打包生成的jar包,是否正常,验证成功!!!!!!!!!!!! 结果正确!! ...

  3. Java 使用poi导入excel,结合xml文件进行数据验证的例子(增加了jar包)

    ava 使用poi导入excel,结合xml文件进行数据验证的例子(增加了jar包) 假设现在要做一个通用的导入方法: 要求: 1.xml的只定义数据库表中的column字段,字段类型,是否非空等条件 ...

  4. jmeter接口测试-调用java的jar包-csv参数化请求-BeanShellPreProcessor生成验签作为请求验证参数-中文乱码----实战

    背景及思路: 需求:要做 创建新卡 接口的测试,要求: 1. 不需要每次手动修改请求参数. 方案:文中先用excle将数据准备好,导出为csv格式,再用jmeter的csv请求进行参数化 2. 卡号需 ...

  5. JAR包结构,META-INF/MANIFEST.MF文件详细说明[全部属性][打包][JDK]

    转载请注:[https://www.cnblogs.com/applerosa/p/9736729.html] 常见的属性 jar文件的用途 压缩的和未压缩的 jar工具 可执行的JAR 1.创建可执 ...

  6. 练习MD5加密jar包编写

    简介 参数签名可以保证开发的者的信息被冒用后,信息不会被泄露和受损.原因在于接入者和提供者都会对每一次的接口访问进行签名和验证. 签名sign的方式是目前比较常用的方式. 第1步:接入者把需求访问的接 ...

  7. Jar包版本查看方法

    原文:  https://blog.csdn.net/u011287511/article/details/66973559 打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目 ...

  8. Dev 日志 | 如何将 jar 包发布到 Maven 中央仓库

    摘要 Maven 中央仓库并不支持直接上传 jar 包,因此需要将 jar 包发布到一些指定的第三方 Maven 仓库,比如:Sonatype OSSRH 仓库,然后该仓库再将 jar 包同步到 Ma ...

  9. 将jar包发布到maven中央仓库

    将jar包发布到maven中央仓库 最近做了一个swagger-ui的开源项目,因为是采用vue进行解析swagger-json,需要前端支持,为了后端也能方便的使用此功能,所以将vue项目编译后的结 ...

随机推荐

  1. [Spark性能调优] 第四章 : Spark Shuffle 中 JVM 内存使用及配置内幕详情

    本课主题 JVM 內存使用架构剖析 Spark 1.6.x 和 Spark 2.x 的 JVM 剖析 Spark 1.6.x 以前 on Yarn 计算内存使用案例 Spark Unified Mem ...

  2. ActiveMQ进阶学习

    本文主要讲述ActiveMQ与spring整合的方案.介绍知识点包括spring,jms,activemq基于配置文件模式管理消息,消息监听器类型,消息转换类介绍,spring对JMS事物管理. 1. ...

  3. Percona XtraBackup的部分备份与恢复/单库备份/单表备份/指定库备份/指定表备份

    本文是翻译的Percona XtraBackup 2.2版的官方文档,原文地址在此:https://www.percona.com/doc/percona-xtrabackup/2.2/innobac ...

  4. VS2015配置内核WDK7600环境,32位下.

    VS2015配置内核WDK7600环境,32位下. 学习内核驱动的编写,就要会配置环境.不然总是用记事本编写.比较不方便. 环境配置如下. 1.首先下载WDK7600, 课堂资料代码中已经上传.链接: ...

  5. Redis 数据结构与内存管理策略(上)

    Redis 数据结构与内存管理策略(上) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...

  6. SpringBoot(二)Web整合开发

    Spring Boot (二):Web 综合开发 本篇文章接着上篇内容继续为大家介绍spring boot的其它特性(有些未必是spring boot体系桟的功能,但是是spring特别推荐的一些开源 ...

  7. HDU 1003 Max Sum【动态规划求最大子序列和详解 】

    Max Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Sub ...

  8. UVA 10382 - Watering Grass【贪心+区间覆盖问题+高精度】

    UVa 10382 - Watering Grass n sprinklers are installed in a horizontal strip of grass l meters long a ...

  9. oracle 11g体系结构

    1.数据库体系结构 1.1 数据库和实例的关系database server  = instance 实例 + database 数据库instance 实例  =内存 memory + 进程  pr ...

  10. MVC学习笔记(分享)

    一.SpringMVC基础入门,创建一个HelloWorld程序 1.首先,导入SpringMVC需要的jar包. 2.添加Web.xml配置文件中关于SpringMVC的配置 <!--conf ...