Java 程序的打包、签名和验证
参考资料
该文中的内容来源于 Oracle 的官方文档。Oracle 在 Java 方面的文档是非常完善的。对 Java 8 感兴趣的朋友,可以直接找到这个总入口 Java SE 8 Documentation ,想阅读什么就点什么。本博客不定期从 Oracle 官网搬砖。这里介绍的工具是 jar 和 jarsigner 。
前言
在前面的 在Linux中安装Oracle JDK 8以及JVM的类加载机制 这一篇中我已经初步讨论过 Java 程序的组成:Java 程序中没有独立函数,只有类和类中的方法,即使是程序的入口点也不是独立函数。 Java 程序的源代码存在于名为 *.java 的源代码文件中,然后经过 javac 命令进行编译,最终可生成名为 *.class 的类文件。 Java 程序的启动器是 java 命令,它负责加载相应的类并执行其中的指令。
Java 程序的这种组织方式和我们常用的文件系统契合度非常好,一个类就是一个文件,类名就是文件名。(当然也有例外,比如内部类,这里不做讨论。)更进一步,Java 中还有 package 的概念,而且一个 package 名(类似于 abc.def.ghi.*.class )正好对应文件系统的路径(类似于 abc/def/ghi/*.class )。这种对应关系不是可有可无的,而是强制性的,我们在组织源代码和类的时候,必须遵守这个准则,否则程序将无法运行。文件多了,自然需要将其打包成一个整体,这就需要用到 jar 命令生成文件名为 *.jar 的 jar 包文件,该 jar 包文件就是一个很常见的压缩包文件,它其中的内容完全维持文件系统中的那种树状结构,随时可以解包查看其中的文件。将库或程序打包成 jar 文件进行发布已经是 Java 世界的标准做法,为了安全, jar 文件还可以被签名和验证。这正是 Java 世界的方便所在。
Java 程序中的 package 名和类文件的路径的对应关系
这里写一个 HelloWorld 程序来做示范。本来一个 HelloWorld 程序是可以很简单的,在 Java 中只需要一个 System.out.println("Hello, World!"); 即可。但是为了让类多一点,我把它写得稍微复杂了点。我先写了一个 Speaker 类,然后在 HelloWorld 类的 main 方法中调用 speaker.sayHello(); 方法来和这个世界打招呼。同时,我的两个类都定义在一个 package 中,如下图源代码中的 package com.xkland.sample; :


前面讲过, package 名必须和文件的路径一一对应,所以,我将源文件放在了 src 目录的 com/xkland/sample 目录中,其实这不是必须的,源文件可以随便放,只是这么放是一个好习惯。但是类文件所在的路径就必须和 package 名完全一致了,否则程序无法执行。如下图,我使用 javac src/com/xkland/sample/*.java -d dst 命令编译源文件,使用 -d dst 选项就是让 javac 把生成的类文件放到 dst 目录中,而 javac 在 dst 目录中自动生成了和 package 名完全一致的目录树。

然后,我们执行程序的时候,必须在 dst 目录中运行 java com.xkland.sample.HelloWorld,在其它的目录中运行都不行,即使在 dst/com/xkland/sample 目录下也不行,哪怕这里是 HelloWorld.class 所在的位置。(其实想在 dst 目录以外的地方运行该程序也有办法,那就是把 dst 目录加入到 CLASSPATH 中,这里不做讨论。)这个例子虽简单,但充分展示了 Java 中的 package 和 类文件在文件系统中的路径 之间必须遵守的约定。将类文件打包也必须遵守这样的约定。
使用 jar 命令将程序打包
让类就这样分散在文件系统中毕竟不是最方便的,前面讲过可以把类文件打包,生成一个 jar 文件,这里来进行实战。(其实打包的文件中可以包含任意类型的文件,不仅只是 Java 的类文件,图片视频什么的都可以,文本文件自然不在话下,这些东西都是资源,这里也不做深入讨论。) jar 包还可以作为一个单独的程序运行,使用 java -jar filename.jar 命令即可。由于每一个 jar 包中包含了不止一个类文件,所以要作为单独的程序运行,在生成 jar 包的时候就必须指定程序的入口点,这个可以通过 jar 命令的 -e 参数指定。
使用 jar 命令打包的时候最重要的注意事项也是前面提到的 package 名和类文件的路径的对应。先看下图:

在该图中,我使用了 jar 命令的 -cfe 选项,其中的 c 是创建 jar 文件,f 是指定 jar 文件的文件名,e 是指定程序的入口点。前面提到过,一定要注意 package 名和类文件的路径的对应关系,所以在这个例子中,使用 jar 命令打包时,要么先进入 dst 目录,再运行 jar -cfe HelloWorld.jar com.xkland.sample.HelloWorld com,要么使用 jar 命令的 -C 指定 jar 包中的类文件的路径从哪个目录开始。我这里用的就是 jar -cfe HelloWorld.jar com.xkland.sample.HelloWorld -C dst com。这里的 com 目录会自动全部打包进 jar 文件,包括其中的所有子目录和文件,也就是说,对于目录的打包是递归的。而且,运行下面这样的命令效果应该是一样的:
jar -cfe HelloWorld.jar com.xkland.sample.HelloWorld -C dst .,这里的.代表当前目录,也就是dst目录中的所有东西都会进行打包;
像上面这样打包后,使用 java -jar HelloWorld.jar 可以运行程序,使用 jar -tf HelloWorld.jar 可以查看 HelloWorld.jar 中的内容。可以看到,类文件的路径为 com/xkland/sample/HelloWorld.class 和 com/xkland/sample/Speaker.class,正好和 package 名完全对应。还可以看到 HelloWorld.jar 中有一个 META-INF/MANIFEST.MF 文件,这个文件是 jar 包文件的灵魂,所有的配置信息都在这里,比如程序的入口点是什么就是保存在这里,它是一个纯文本文件,可以直接读写,但是我们实际工作中基本不需要自己手动编写该文件,所以这里不做深入讨论。
如果使用 jar 打包的时候没有选择正确的开始目录,则 jar 包中类文件的路径就会不正确,程序就无法运行。如下图,打包时既没有进入 dst 目录,又没有使用 -C dst 选项,结果打包后程序就无法正确运行了。使用 jar -tf HelloWorld.jar 查看一下,发现所有类文件的路径都不对,因此程序无法运行。

关于 jar 的更多内容,可以直接查看 jar命令的手册,或者查看 Java教程中关于jar的章节 。
jar 包的签名和验证
在我介绍 JDK中的证书生成和管理工具keytool 时,已经简单的讲过网络安全、证书、签名等方面的内容,这里只需要实战一下即可。现在已经有了一个 HelloWorld.jar 文件,尝试一下使用 jarsigner 命令对它进行签名。签名之前,先得有个证书,所以先使用 keytool -genkeypair -alias youxia 为自己创建一个,别名为 youxia。然后,使用这个证书对 HelloWorld.jar 进行签名,命令为 jarsigner HelloWorld.jar youxia。最后,可以使用 jarsigner -verify HelloWorld.jar 对 HelloWorld.jar 进行验证。如下图:

不动手不知道,一动手才发现 So Easy!更多的细节可以戳 Signing JAR Files 和 Verifying Signed JAR Files。
jar 文件被签名后,里面多了一些文件,把它解包看一下,如下图:

可以看到:首先是 MANIFEST.MF 文件中多了几行,它为 jar 包中的每一个文件都生成了一个数据摘要,这个摘要是从 jar 包中包含的文件本身计算出来的;其次,多了一个 YOUXIA.SF 文件,其中的内容也是 jar 包中每个文件对应的摘要,但是这个摘要是从 MANIFEST.MF 中的数据项计算出来的,它同时包含有针对整个 MANIFEST.MF 文件计算出的摘要;最后,就是一个无法直接阅读的文件 YOUXIA.DSA,从图中可以看出这个文件显示为乱码,其中的内容就是 youxia 的公钥以及使用 youxia 的私钥对该 jar 文件进行签名后的结果。具体信息请看 Understanding Signing and Verification。
有了这里的直观的印象,我们就对签名和验证有了更深入的了解。签名和验证是建立在信息摘要算法和非对称加密解密算法的基础上的。数据摘要算法是不可逆的,它只能从数据生成摘要,不能从摘要解密出数据。非对称加密解密算法需要公钥私钥对,使用私钥加密的数据只能使用公钥解密,因此使用私钥对上一步生成的摘要进行加密,就相当于是签名了,因为只能通过相应的公钥进行解密。一般情况下公钥是通过证书发布出去的,而在上面的例子中,签名者的公钥直接放在了 YOUXIA.DSA 文件中,方便验证者使用。
总结
jar 和 jarsigner 这两个命令用起来没有什么难度,主要是理解其中的思想。使用 jar 时,一定要注意 package 名和类文件的路径之间的对应关系;使用 jarsigner 时,要理解公钥私钥、证书、摘要和数字签名,而且 JDK 中提供了非常好用的生成和管理公钥私钥及证书的工具 keytool。对于这些工具,我们只要亲自动手试一下,就可以加深我们对 Java 安全方面的理解。至于这些命令的细节都不需要多记,用的时候查官方文档即可。
Java 程序的打包、签名和验证的更多相关文章
- Java初学者作业——编写Java程序,实现用户登录验证。
返回本章节 返回作业目录 需求说明: 编写Java程序,实现用户登录验证. 若用户名与密码输入正确,则提示"登录成功,欢迎回来!",若用户名与密码不匹配,则提示"用户名和 ...
- 一招教你IDEA中Java程序如何打包,以及打包后如何运行
前言 编写程序 程序打包 测试运行 IDEA作为目前按最主流的Java程序项目编写工具,越来越受到开发人员的青睐.idea因为其五花八门的功能,让你在开发过程中效率显著提高.那么对于初学者来说,如何通 ...
- 第一次发博,发个简单的Java程序发送手机短信验证
最近在准备一个项目,想的登录时候用手机验证,就通过上网查阅了一下手机验证的实现方法,原来超级简单,下面将一步一步介绍. 1.去中国网建注册一个账号密码,首次注册送五条免费短信和3条免费彩信.具体的网址 ...
- 使用 SecurityManager 和 Policy File 管理 Java 程序的权限
参考资料 该文中的内容来源于 Oracle 的官方文档.Oracle 在 Java 方面的文档是非常完善的.对 Java 8 感兴趣的朋友,可以从这个总入口 Java SE 8 Documentati ...
- Java程序实现密钥库的维护
1 Java程序列出密钥库所有条目 import java.util.*; import java.io.*; import java.security.*; public class ShowAli ...
- Android程序的打包和安装
当我们使用Android Studio的时候,这些步骤都交给它去做了. 编译 classes.dex 文件 编译 resources.arsc 文件 生成资源索引表resources.arsc. 把r ...
- Python中调用Java程序包
<原创不易,转载请标明出处:https://www.cnblogs.com/bandaobudaoweng/p/10785766.html> 开发Python程序,需求中需要用到Java代 ...
- Java程序设计基础作业目录(作业笔记)
持续更新中............. Java程序设计基础笔记 • [目录] 我的大学笔记>>> 第1章 初识Java>>> 1.1.4 学生成绩等级流程图练习 1 ...
- 关于java程序打包为EXE的若干问题
这几天在一个即时通讯系统的打包上,吃尽了苦头,到现在才算解决,现在对遇到的问题进行分析总结. 1.一开始是在export "Runnable JAR file"的时候,弹出了这样的 ...
随机推荐
- Hive安装配置指北(含Hive Metastore详解)
个人主页: http://www.linbingdong.com 本文介绍Hive安装配置的整个过程,包括MySQL.Hive及Metastore的安装配置,并分析了Metastore三种配置方式的区 ...
- AJAX实现登录界面
使用php跳转界面和AJAX都可实现登录界面的跳转的登录失败对的提醒.但是,php跳转的方式 需要额外加载其他界面,用户体验差.AJAX可实现当前页面只刷新需要的数据,不对当前网页进行 重新加载或者是 ...
- CSS 3学习——transition 过渡
以下内容根据官方规范翻译以及自己的理解整理. 1.介绍 这篇文档介绍能够实现隐式过渡的CSS新特性.文档中介绍的CSS新特性描述了CSS属性的值如何在给定的时间内平滑地从一个值变为另一个值. 2.过渡 ...
- 用MongoDB分析合肥餐饮业
看了<从数据角度解析福州美食>后难免心痒,动了要分析合肥餐饮业的念头,因此特地写了Node.js爬虫爬取了合肥的大众点评数据.分析数据库我并没有采用MySQL而是用的MongoDB,是因为 ...
- C#开发微信门户及应用(39)--使用微信JSSDK实现签到的功能
随着微信开逐步开放更多JSSDK的接口,我们可以利用自定义网页的方式来调用更多微信的接口,实现我们更加丰富的界面功能和效果,例如我们可以在页面中调用各种手机的硬件来获取信息,如摄像头拍照,GPS信息. ...
- Android Weekly Notes Issue #235
Android Weekly Issue #235 December 11th, 2016 Android Weekly Issue #235 本期内容包括: 开发一个自定义View并发布为开源库的完 ...
- mysql 大表拆分成csv导出
最近公司有一个几千万行的大表需要按照城市的id字段拆分成不同的csv文件. 写了一个自动化的shell脚本 在/home/hdh 下面 linux-xud0:/home/hdh # lltotal 1 ...
- Linux根文件系统分析之init和busybox
Hi,大家好!我是CrazyCatJack.今天给大家讲解Linux根文件系统的init进程和busybox的配置及编译. 先简单介绍一下,作为一个嵌入式系统,要想在硬件上正常使用的话.它的软件组成大 ...
- H5图片上传插件
基于zepto,支持多文件上传,进度和图片预览,用于手机端. (function ($) { $.extend($, { fileUpload: function (options) { var pa ...
- 如何设计一门语言(七)——闭包、lambda和interface
人们都很喜欢讨论闭包这个概念.其实这个概念对于写代码来讲一点用都没有,写代码只需要掌握好lambda表达式和class+interface的语义就行了.基本上只有在写编译器和虚拟机的时候才需要管什么是 ...