Saturday, December 28, 2013

Syntax Highlighter 使用方法

作为一个开发人员,经常需要在一些技术blog中经常想要插入一些代码段。SyntaxHighlighter是一个开源的js库,作用是可以仅仅通过JavaScript将Html中的代码段以与IDE类似的样式呈现出来。

Syntax Highlighter的特点:
  1. 完全以client端脚本实现,也就是说不需要链接服务器,仅仅通过在浏览器中执行JavaScript即可完成功能。这使得Syntax Highlighter可以被方便的集成到blogger上,尽管自己的blog是host在blogger.com上的。
  2. 支持多种Theme。通过引入不同的css文件,可以方便的在各种Theme之间切换。
  3. 支持多种编程语言。它基本涵盖了所有目前主流的开发语言,可以通过制定brush,来告知Syntax Highlighter以何种语言模板来呈现你的代码段。

使用方法
首先,引入Syntax Highlighter需要的CSS和JavaScript:
<link rel="stylesheet" href="styles/shCore.css">
<link rel="stylesheet" href="styles/shThemeDefault.css">
<script type="text/javascript" src="scripts/shCore.js"></script>
接下来需要调用SyntaxHighlighter的初始化方法,控制SyntaxHighlighter对页面的代码段进行美化:
<script>
    SyntaxHighlighter.all();
</script>
注意这个方法可以在任意的地方调用,不需要等待文档加载完毕。可能会觉得奇怪,如果文档没有加载完毕,如何来确定美化哪些界面元素呢?通过查看源码,我们可以发现,其实原因特别简单:在shCore.js中有这样一段代码:
/**
 * Main entry point for the SyntaxHighlighter.
 * @param {Object} params Optional params to apply to all highlighted elements.
 */
all: function(params)
{
    attachEvent(
        window,
        'load',
        function() { sh.highlight(params); }
    );
}
这也就是调用all方法执行的代码。从中可以看出,尽管我们在外部没有等到文档加载完毕,但是在all方法内部,实际上已经是将程序的入口方法放在了load事件中,也就是文档加载完毕后再执行。

其实我们并没有引入足够的js文件,我们还缺少一系列brush文件,这个一会儿再说。

接下来我们要告诉Syntax Highlighter代码段在哪里。想要达成这一点有两种方法:
  1. 使用<pre>...</pre>元素对代码段进行包装:很简单,将代码段放在一对<pre>标签中,并且给pre标签加入class属性,用来指定一些参数。其中必须的参数的brush——用来指明这段代码是何种语言,之后SyntaxHighlighter就会使用这种语言的模板,对这段代码进行呈现。例:
    <pre class="brush: js">
        /**
         * SyntaxHighlighter
         */
        function foo()
        {
            if (counter <= 10)
                return;
            // it works!
        }
    </pre>
    
    上面的代码将呈现为如下的结果:
    /**
     * SyntaxHighlighter
     */
    function foo()
    {
        if (counter <= 10)
            return;
        // it works!
    }
    
    使用这种方法有一个弊端,那就是必须要对代码中的HTML特殊字符进行转义,比如上例的<就需要使用HTML Escape Tool预先进行处理。
  2. 使用<script><![CDATA[…]]></script>元素对:其实是将代码段包裹在<script><![CDATA[…]]></script>里面。同样需要指定script元素的class属性来向SyntaxHighlighter传递参数,brush属性同样是必要的。另外这里需要指定script的type属性为syntaxHighlighter。例:
    <script type="syntaxhighlighter" class="brush: js"><![CDATA[
    /**
     * SyntaxHighlighter
     */
    function foo()
    {
        if (counter <= 10)
            return;
        // it works!
    }
    ]]></script>
    
    上面的代码将呈现如下结果: 这种方法的好处是,我们不需要对代码段中的HTML特殊字符进行转换了。
    但是这种方法也有弊端,多数的RSS阅读器会过滤掉script标签,因为如果你的站点会被RSS阅读器读取,那么建议使用第一种方式。

引入shBrushXxx.js
上面留下了关于Brush的问题。比如上面两个例子中我们需要渲染的都是JS代码,因此我们需要引入shBrushJScript.js. 所以同样将下面的语句放在head标签内:
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'></script>
到此为止我们保存Template,和我们post,刷新页面即可看到被渲染过的代码段了。

shAutoloader.js
大家可能已经注意到了,这里存在一个性能问题。通常我们的网站是动态的,也就是说多个页面使用同一个模板,通过填充数据的方式,来生成不同的页面。那么这里问题就出现了,在模板中我们必须预先知道这个页面中需要显示哪种类型的代码段。比如,需要显示Java 代码,就需要引入shBrushJava.js;需要显示C#代码段,就需要引入shBrushCSharp.js。如果说在编写模板页的时候,不能确定会显示哪些类型的代码段时,事情就有些麻烦了。一种保守的解决方案是把所有SyntaxHighlighter支持的编程语言的Brush.js文件都引入。但是这无疑是一种浪费,因为,很有可能显示的页面只用到了其中一个Brush.js。
如何来解决上面的问题呢,在SyntaxHighlighter 3以前是没法解决的。SyntaxHighlighter 3中提供了一种优化的方式来解决上面的问题——shAutoloader.js
这个js文件的功能是自动加载需要的Brush文件。听起来非常酷吧~在实际使用的时候,并不是太酷。因为官方的文档对这一部分描述的太过于简单,但是实际上想要使用这个功能,有许多地方需要注意,我是通过一点点实验和查看源码才理解了使用的方法。
官方文档上说,使用shAutoloader只需要引入shAutoloader.js。然后调用SyntaxHighlighter.autoloader 方法添加brush alias 到 brush.js文件的映射即可。
<script>
    function path() {
        var args = arguments;
        var result = [];
        for (var index in args) {
            args[index][args[index].length - 1] = args[index][args[index].length - 1].replace('@', 'scripts/');
            result.push(args[index]);
        }
        return result;
    }
    SyntaxHighlighter.autoloader.apply(null, path(
        ['applescript', '@shBrushAppleScript.js'],
        //此处省略大约22行
        ['xml', 'xhtml', 'xslt', 'html', '@shBrushXml.js']
    ));
    SyntaxHighlighter.all();
</script>
这段代码没有错,按照这种方法的确可以让代码选正确显示。但是官方文档里面忽略了一个问题——这段脚本放在哪执行呢?必须是文档加载之后么?大家可能还记得SyntaxHighlighter.all方法可以在任一地方调用,那么SyntaxHighlighter.autoloader方法是不是也一样呢?
经过查看源代码,和多次的debug,我现在可以给出准确的答案:
  1. SyntaxHighlighter.autoloader方法必须在文档加载之后进行调用。原因是autoloader的代码中没有把要执行的逻辑attach到load事件里面,所以它不具有SyntaxHighlighter.all方法那种灵活性。如果autoloader方法在文档加载之前执行了,那么你不会收到任何错误提示,SyntaxHighlighter会以为文档中不存在任何代码段,进而不会进行任何美化操作;
  2. SyntaxHighlighter.all方法必须要在SyntaxHighlighter.autoloader方法之后调用。如果不这样做,将收到一个找不到相应Brush的错误。这个很容易理解吧,因为Brush要在autoloader方法执行之后才会被加载,autoloader方法还没有执行呢,当然就没有Brush啦。
总的来说,Syntax Highlighter是个好东西,autoloader我却不敢恭维。因为太依赖于使用者的调用方式了,这样是不健壮的。

那么就像上面所说,如何使我的代码段在文档加载完毕后执行呢?这有好多种方式了:
  1. 将脚本段放在</body>之前,保证在执行脚本的时候body中的元素已经加载完毕;
  2. 和all的实现方法一样,将自己的方法attach到document的load事件上;
  3. 使用jQuery,在文档准备好后执行你的方法。
这里不讨论哪种方法更好,我使用了第三种,因为页面可能会有别的功能使用到jQuery,所以感觉第三种更适合我。为了方便,我把代码也放上来:
<head>
    <link rel="stylesheet" href="styles/shCore.css">
    <link rel="stylesheet" href="styles/shThemeDefault.css">
    <script type="text/javascript" src="scripts/shCore.js"></script>
    <script type="text/javascript" src="scripts/shAutoloader.js"></script>
    <script src="jquery-1.10.2.min.js"></script>
    <script>
        function path() {
            var args = arguments;
            var result = [];
            for (var i=0;i<args.length;i++) {
                args[i][args[i].length - 1] = args[i][args[i].length - 1].replace('@', 'js/syntaxhighlighter/scripts/');
                result.push(args[i]);
            }
            return result;
        }

        SyntaxHighlighter.config.bloggerMode = true;
        SyntaxHighlighter.defaults["toolbar"] = false;

        $(function() {
            SyntaxHighlighter.autoloader.apply(null, path(
                ['applescript', '@shBrushAppleScript.js'],
                ['actionscript3', 'as3', '@shBrushAS3.js'],
                ['bash', 'shell', '@shBrushBash.js'],
                ['coldfusion', 'cf', '@shBrushColdFusion.js'],
                ['cpp', 'c', '@shBrushCpp.js'],
                ['c#', 'c-sharp', 'csharp', '@shBrushCSharp.js'],
                ['css', '@shBrushCss.js'],
                ['delphi', 'pascal', '@shBrushDelphi.js'],
                ['diff', 'patch', 'pas', '@shBrushDiff.js'],
                ['erl', 'erlang', '@shBrushErlang.js'],
                ['groovy', '@shBrushGroovy.js'],
                ['java', '@shBrushJava.js'],
                ['jfx', 'javafx', '@shBrushJavaFX.js'],
                ['js', 'jscript', 'javascript', '@shBrushJScript.js'],
                ['perl', 'pl', '@shBrushPerl.js'],
                ['php', '@shBrushPhp.js'],
                ['text', 'plain', '@shBrushPlain.js'],
                ['py', 'python', '@shBrushPython.js'],
                ['ruby', 'rails ror rb', '@shBrushRuby.js'],
                ['sass', 'scss', '@shBrushSass.js'],
                ['scala', '@shBrushScala.js'],
                ['sql', '@shBrushSql.js'],
                ['vb', 'vbnet', '@shBrushVb.js'],
                ['xml', 'xhtml', 'xslt', 'html', '@shBrushXml.js']
            ));
            SyntaxHighlighter.all();
        });
    </script>
</head>

切换Theme
SyntaxHighlighter提供了许多Theme,作为用户,我们可以通过引入不同的css文件来方便的切换主题。上面我们引用了一个shThemeDefault.css这个css文件,可以将其替换为不同主题的css文件。比如shThemeEclipse.css,就会让代码段被渲染成Eclipse的风格。

配置Syntax Highlighter
Syntax Highlighter提供了很多可供配置的参数,有三种方式对Syntax Highlighter进行配置:
  1. 全局属性的配置
    比如想要把Syntax Highlighter集成到Blogger.com上,那么就需要配置这个全局属性:
    SyntaxHighlighter.config.bloggerMode = true; 
    
  2. 局部参数配置
    比如想要把代码段旁边的行号去掉,可以这样配置:
    <pre class="brush: xxx; gutter: false;"> 
    
  3. 默认局部参数配置
    其实就是对局部参数配置的一个补充,比如想要把所有的代码段的gutter都设置为false,那么可以这样做:
    SyntaxHighlighter.defaults["gutter"] = false;
    
大家可能也注意到了,代码段的右上角有个比较讨厌的"?",如何去掉呢?
function toolbarFun() {
    //example
}
针对这一点Syntax Highlighter提供了配置项,我们就可以通过修改默认局部参数配置来达到隐藏"?"的目的:
SyntaxHighlighter.defaults["toolbar"] = false;
Syntax Highlighter提供的配置项还有很多,这里就不一一列举了,详细的参数列表请看这里

因为我也是刚刚开始使用Syntax Highlighter,如有不准确的地方,敬请指正。

Thursday, December 26, 2013

JCA Engine - SecureRandom

SecureRandom 是JCA中最简单的Engine类。它的作用是生成随机数。很多和Java Security相关的操作,都离不开随机数。比如,KeyGenerator需要随机数生成器来随机生成key;Cipher可能需要随机数生成来生成IV(Initialization Vector)。

java.security.SecureRandom vs java.util.Random

注意:为了方便,下文使用SecurityRandom代替java.security.SecureRandom;使用Random代替java.util.Random.

说道SecureRandom就不得不说我们比较熟悉的Random类。这两个类都是随机数生成器,那么它们之间有什么区别呢?Java API为什么要提供两个完成相同功能的类呢?

在Random的API文档中有这样一段话:

Instances of java.util.Random are not cryptographically secure. Consider instead using SecureRandom to get a cryptographically secure pseudo-random number generator for use by security-sensitive applications.

大致的意思是说,Random的实例是不安全的。如果想要在安全级别比较高的系统中使用随机数生成器,请考虑使用SecureRandom.

也就是说Random不安全。我在读到这里的时候就产生了一个疑问:随机数生成器有什么不安全的呢?它的“不安全表现在什么地方呢”?经过一番研究,了解到这里所谓的不安全,实际上是说,生成的随机序列是可以被预测的。也就是说,我知道了几个生成的随机数后,就可以推测出接下来将会生成的随机数是什么。

发现有一点可怕了吧… 设想这样一种情况:用户登录系统之后,系统会生成一个随机数作为用户操作系统的token,并提供给用户。这个用户每次操作的时候,都需要提供这个token。此时,如果这个随机数生成器是不安全的,那么黑客则可以预测出来生成的随机数序列,进而预测出将会分配给用户的token。这样一来,也就可以未经过用户允许的情况下,操作用户的数据。

关于如何预测Random生成的随机数,可以参考这篇文章。我从这篇文章中摘录了一些代码,用来演示破解随机数生成器的过程。

这段代码会连续输出10个随机数:
Random random = new Random();
for (int i=0;i<10;i++) {
    int randomValue = random.nextInt();
    System.out.println(String.format("v%d: %s", i + 1, randomValue));
}
本次运行结果是这样的:
v1: -1116240189
v2: 1675702140
v3: -227598907
v4: -452268948
v5: 2006095225
v6: 584972179
v7: -1039927173
v8: -656175616
v9: -704109609
v10: 11273124
作为黑客只需要知道任意两个连续的随机数,就可以破解这个随机数生成器了。为了简单,我假设黑客知道了v1和v2:
long multiplier = 0x5DEECE66DL;
long addend = 0xBL;
long mask = (1L << 48) - 1;

int v1 = -1116240189;
int v2 = 1675702140;

for (int i = 0; i < 65536; i++) {
    long seed = (((long) v1) << 16) + i;
    if (((seed * multiplier + addend) & mask) >>> 16 == v2) {
        System.out.println("Seed found: " + seed);
        break;
    }
}
黑客通过执行这段代码,将得到如下结果:
Seed found: -73153916970526
看到了吗,我们反向计算出来了这个随机数生成器的seed。已经接近成功了不是吗?剩下的我们只要初始化一个Random对象,并且设置它的seed为-73153916970526,就可以得到和系统中一样的随机数序列:
long multiplier = 0x5DEECE66DL;
long mask = (1L << 48) - 1;
/*
这里之所以要对seed的值处理一下,是因为我们通过计算得到的seed,是经过了Random类处理过的,并不是系统创建Random时传入的值,因此这个seed是不能通过Random的构造方法传入的。请参见Random.initialScramble这个方法。这里只是做了这个方法的反向运算,使其最后设置的seed符合我们计算出来的值。
*/
long seedValue = -73153916970526L ^ multiplier;
Random random = new Random(seedValue);
for (int i=0;i<10;i++) {
    int predictValue = random.nextInt();
    System.out.println(String.format("v%d: %s", i + 1, predictValue));
}
运行程序,得到如下结果:
v1: 1675702140
v2: -227598907
v3: -452268948
v4: 2006095225
v5: 584972179
v6: -1039927173
v7: -656175616
v8: -704109609
v9: 11273124
v10: -1299046198
咱们可以对比着看一下,这样更明显些:
系统输出 黑客计算
v1: -1116240189 --
v2: 1675702140 v1: 1675702140
v3: -227598907 v2: -227598907
v4: -452268948 v3: -452268948
v5: 2006095225 v4: 2006095225
v6: 584972179 v5: 584972179
v7: -1039927173 v6: -1039927173
v8: -656175616 v7: -656175616
v9: -704109609 v8: -704109609
v10: 11273124 v9: 11273124
-- v10: -1299046198
看到了吗,黑客通过两个连续的随机值,计算出了之后所有的随机序列。

这就是我们为什么要在安全要求比较高的系统中使用SecureRandom的原因。

那么这里就又有一个疑问了:既然SecureRandom要比Random安全,我们不如把所有使用Random的代码都换成SecureRandom,从而使系统更加安全,不是吗?当然不是啦!!

要知道“有一利,必有一弊”。这里是“以性能换安全”。也就是说,SecureRandom的性能要比Random的性能差,因此在一些与安全无关的情况下,还是建议使用Random,来达到更高的效率。关于为什么会有效率问题,请参考这篇文章

使用方法

好了,是时候说说怎么使用SecureRandom了。其实使用SecureRandom类特别简单,首先说明,这是JCA中的一个Engine类(参考本类主题的Overview)。那么提到Engine类,我们就知道怎么样获得它的实例了:

SecureRandom.getInstance(algorithm);

通过传入一个生成随机数的算法名称,向JCA请求一个实现了这个算法的随机数生成器。默认的随机数生成器算法是SHA1PRNG;也可以选择性地传入一个provider,如果没有传入provider,那么JCA会根据查找策略,从注册到JCA的Provider中查找实现了这个算法的SPI。

接下来可以选择性的调用setSeed方法来设置这个随机数生成器的seed。如果不设置seed,那么SecureRandom的实现类将会完全随机产生随机序列。

如果把两个SecureRandom设置为相同的seed,那么这两个SecureRandom将产生相同的随机序列。

可以随时再次调用setSeed方法,来更新seed。但是请注意,重新设置seed的时候,并不是简单的用新的seed替换原有seed,而是将新的seed作为原有seed的补充。这样做的好处是,当使用一个值多次调用setSeed后,得到的依然是完全随机的序列,不会产生于之前相同的序列。请看下面的演示:
random1.setSeed(160L);
System.out.println(String.format("Random1: %d", random1.nextInt()));
random1.setSeed(160L);
System.out.println(String.format("Random1: %d", random1.nextInt()));
random2.setSeed(160L);
System.out.println(String.format("Random2: %d", random2.nextInt()));
以上程序中random1 和 random2 分别是两个刚刚实例化的SecureRandom对象。注意,这两个random对象都没有设置过seed。执行以上程序,将得到下面的结果:
Random1: 718216388
Random1: 862077552
Random2: 718216388
由此可见,在random1上,第二次将seed设置成160也无法使SecureRandom变回第一次设置完seed时候的状态了,因此证明了重新设置seed不会替换原有的seed,而是作为原有seed的补充。

而random2是第一次设置seed,而且seed与random1 的第一个seed相同,所以能够生成与random1相同的随机序列。

seed设置完毕后就可以调用SecureRandom中的nextXxx方法来获得随机数,这里API中讲解的非常详细,这里不再赘述。

其中有一个方法使用起来比较方便,而且我之前没有注意到。在这里提一下:

synchronized public void nextBytes(byte[] bytes)

通过传入一个byte数组,这个方法将会用随机的byte填充整个byte数组,这个方法用于生成IV或者Key的时候非常方便,值得记住。

Monday, December 23, 2013

JCA - Java Cryptography Architecture Overview

JCA是Java关于加密解密以及方法摘要等一系列相关操作的API。JCA本身并不包含任何实现。它只是提供了API接口,供服务提供者进行实现。另一方面JCA又进行了服务的抽象。使得在开发过程中,不需要关心服务的具体实现。比如,我想使用SHA-256算法,对一段消息进行消息摘要。我只需要向JCA发出请求:“我需要一个实现了SHA-256算法的消息摘要服务对象”。JCA会根据特定的算法寻找对应实现了这个算法的服务类,并实例化其对象,返回给开发者。

在JCA中分别有几种不同的类:Engine类,SPI类,Provider类。一下会分别解释这几种类的作用以及关系。

Engine类
Engine类是供外部使用某种特定服务的类。比如,用于消息摘要的MessageDigest类;用于生成随机数的SecureRandom类;用于加密的Cipher类等等。它并不完成具体的功能,而是将所有的操作,通过方法调用的方式,转发到它所依赖的Spi类中,并由Spi类来完成功能。

在使用Engine类的时候有一些通用的规则:
  1. Engine类无法直接通过构造方法来实例化。只能通过调用其静态的getInstance方法获得实例。调用getInstance方法时,需要一个算法名称和一个可选的Provider。 
  2. 拿到Engine类的实例之后,可能需要调用它的Init方法(根据不同的Engine类可能有不同Init方法),对这个Engine对象进行初始化。不同的Engine类,需要不同的初始化参数。 
  3. 调用特定Engine相关的一些方法,执行需要的操作。这一步差异较大,具体情况,具体分析。(doFinal, digest 等...) 

SPI(Service Provider Interface) 类
提供某种服务的特定算法实现。他们都继承了对应的XxxSpi接口。比如,采用MD5算法进行消息摘要的MD5类和采用SHA256算法进行消息摘要的SHA2类,继承了MessageDigestSpi;采用AES算法进行数据加密的AESCipher类和采用DES算法进行数据加密的DESCipher类,继承了CipherSpi。
根据上面Engine类的描述,Engine类会把实际的操作委托给SPI类来完成。

Provider类
首先要区分下Provider和Provider类。实际上可以将Provider理解为一个或一组jar包。这些jar包中包含了一些实现好的SPI类。这些jar包可能是Java安装包中自带的,也可能是某些组织为了使用一些特定的算法,而自己开发的,也可能是从网上下载的第三方jar包。无论渠道是怎样的,每一组jar包就称为一个 Provider。那么Provider类就是用于描述和初始化这个Provide的。比如告知JCE这个Provide中提供了哪些服务,以及实现了特定服务的哪些算法等。因此每一组可以被称为Provider的jar包中,都会包含一个用于描述这个Provider的Provider类。

Engine/SPI/Provider之间的关系
  • Provider类描述了Provider本身(name, version...),以及它所包含的SPI,JCA会从Provider类中获取这些信息;
  • Engine类依赖于SPI类来完成功能,每一个Engine类内部都会有一个以Spi结尾的属性。Engine类就是通过这个对象来调用Spi中的方法的;
  • 当向JCA请求一个Engine时(调用getInstace),JCA会根据请求时的参数,并根据Provider类中提供的信息,找到对应的SPI类,并实例化之。将SPI对象装配到Engine对象中返回。从而供外部调用Engine类中的方法时,进行操作转发。
Built-in Provider
在JDK的安装文件中,包含了11个内置的Provider:SunPKCS11, SUN, SunRsaSign, SunJSSE, SunJCE, SunJGSS, SunSASL, XMLDSig, SunPCSC, SunMSCAPI, SunEC。关于每种Provider中提供了什么服务,请参见Java的官方文档

这就是JCA的一个简单的描述,只有正确理解了这个通用的架构,才能更好的理解Java Security。