SSH端口映射

SSH提供了两种端口映射方式,一种是本地端口映射,另一种是远程端口映射。

使用本地端口映射,SSH客户端会在本地监听一个端口,所有到该端口的连接全部转发到SSH服务器上指定的主机和端口号上。例如,我需要在我自己的电脑上访问VPS服务器上的数据库,但是服务器上的MySQL绑定在了127.0.0.1的IP地址上,可以用下面的命令把我电脑上的33306端口转发到服务器上的3306端口。

ssh xts.so -l root -L 33306:localhost:3306

运行这个命令后,我就可以直接用MySQL客户端连接自己电脑的33306端口访问服务器数据库了。

SSH的-L参数完整格式是-L [bind_address]:port:host:hostport,其中bind_address是本机(客户端)的绑定IP,可以省略,缺省值是127.0.0.1,如果需要允许其它电脑访问,可以监听0.0.0.0或者简单写个*port是本机开始监听的端口,这个端口的连接会转发到服务器上;host是相对于服务器而言的目标主机地址,域名、hostname、IP地址均可;hostport是目标主机的端口号。

3497093050.svg

如果需要在本地内网访问服务器机房内网的数据库,只需要指定binding_addresshost就可以了。

远程端口映射,SSH会在服务器上监听一个端口,所有到该端口的连接全部转发到SSH客户端指定的主机和端口号上。例如,我需要让本机的80端口可以在公网访问,却又不能独立使用公网IP,可以找一台VPS服务器作远程端口映射。

ssh xts.so -l root -R *:8080:localhost:80

运行这个命令后,用浏览器打开http://xts.so:8080/就相当于访问我本地电脑的80端口。

SSH的-R参数完整格式是-R [bind_address:]port:host:hostport,其中bind_address是远程(服务器)的绑定IP,可以省略,缺省值也是127.0.0.1;port是远程服务器上开始监听的端口;host是相对于本地电脑而言的目标主机地址;hostport是目标主机的端口号。

SSH远程映射也叫反向端口映射,因为常用于把内网主机的22端口暴露到公网。

ssh xts.so -l root -R *:2222:localhost:22 -N -f

这个命令可以把服务器的2222端口映射到本机的22端口,从而允许从Internet登录内网服务器。-N告诉ssh不要运行任何命令,此连接只用于端口映射。-f选项可以让ssh转入后台运行,这样ssh命令运行之后就能回到命令行,可以继续使用其它命令。

如何安全地保存用户的密码

最近数据库泄漏事件层出不穷,无数人的上网密码被人破解。本文来探讨一下如何安全地保存用户的密码。

为什么非要安全地保存密码?因为人类使用密码有三大偏好:

  1. 喜欢用简单好记的密码
  2. 喜欢到处用相同的密码
  3. 不喜欢经常地修改密码

为了你的客户不至于因为你的数据库泄漏事故和损失惨重,请保护好他们的密码!

最烂的方式:明文保存

许多早期制作的网站,还有众多的政府网站,都是这么保存密码的,包括著名的CSDN也是。有的时候这么保存密码是不得已的:我曾经接到过一个政府部门的项目,领导要求在他忘记密码的时候能让单位负责IT工作的小王帮助查一下密码是什么。不过还好一般并不难说服领导换另一种方式:如果您忘了密码,可以用手机重设密码。

明文保存密码的方法把密码安全完全交给了运维。任何安全漏洞,不论是操作系统漏洞,还是数据库漏洞,甚至应用程序中的漏洞,都会导致用户的密码大白于天下。

次烂的方式:MD5保存

相对明文密码好一点的方法是把用户的密码直接哈希保存。可惜大多数用这种方式保存密码的人并不是因为意识到明文保存密码有什么不妥,仅仅是因为学习编程的时候教材上是这么做的。利用单向哈希算法保存密码当然能比明文保存好一点点,但其最大的问题却在于会让开发者误以为用户的密码是非常安全的——即使泄漏了数据库,黑客也不可能知晓用户的密码是什么。

且不说MD5已经被证明是非常不安全的哈希算法,即使换成SHA-1或者复杂度更高的哈希算法,也不可能显著地提升用户密码的安全性,因为黑客攻击的方式往往并不是通过数学方法寻找哈希碰撞,而是直接在字典中查询。每个黑客手上都有上千万条记录的密码字典,包括常用的单词、拼音、19xx到20xx年的生日等等。他们只需要把MD5的结果输入,就能在字典库中找到对应的原文。一般一个MD5的密码库泄漏的时候,超过八成密码能在字典中反查得到。

比较好的方式:加盐哈希保存

如果定义一个长字符串,把它插入到用户密码中的某个地方,然后再哈希出结果,这样可以改变用户密码的哈希结果,使字典攻击失效。

比如用户的密码是abc123,直接MD5的结果是e99a18c428cb38d5f260853678922e03,大多数黑客的字典中都有这条记录。如果我们把用户的密码加上这个前缀ask3Kxsk777sA00bdsOo552,变成ask3Kxsk777sA00bdsOo552abc123,然后再进行MD5计算,得到的就是3ee795c4ceadf8b21a12f6e373cb1c56,一般黑客的字典里都不会有这条记录。这里用到的前缀就被称作“盐”

加盐哈希保存的结果是,黑客需要同时取得你的数据库和你的“盐”,并且加盐重新生成整个字典库才能破解你的用户密码。安全性比直接哈希保存要高多了。

更好的方式:随机加盐哈希保存

这是对于上一种方法的改进。在前面的方法中,盐是固定的,加盐的位置是固定的,以当今的计算机速度,黑客只需要多花点心思把你的盐搞到手,然后再花个一两天把密码库加盐跑一遍,还是能破解你大多数用户的密码。

如果在保存密码的时候盐随机生成,并插入到原始密码的随机位置,那么数据库里每条密码记录的盐和加盐位置都不同,黑客如果要破解密码,就需要为每一个密码生成一遍字典库,工作量要大得太多了。

最好的方法:没有密码

最安全的密码保存方法就是完全不保存用户密码。现在各大社交网站都支持账户接入非常发达,完全可以让用户用微博、微信、QQ、豆瓣、淘宝、人人、Google、Yahoo、Twitter、Facebook等等等等各种第三方账户来登录你的系统,再不济也可以让用户用随机短信密码来登录。没有保存密码,就不会丢失密码。

别人推荐的方式:慢哈希

慢哈希是一种特别的哈希算法,它比MD5、SHA1等常见的密码哈希算法要慢得多。它的安全原理是:在用户注册或者登录等正常行为时,哈希函数的运行时间由几毫秒变慢为几百毫秒,在用户感受上不会有太大的差别,而对于攻击者来说,因为需要大量计算哈希值试错,慢哈希函数就能有效延长破解密码所需的时间。

用Canvas技术压缩要上传的图片

背景

现在摄像头已经是手机的标配了,移动网站也做得越来越像APP。然而拍照上传这件事情的体验似乎仍然不如APP,主要原因是现在手机拍摄的照片太大,上传非常消耗流量也非常耗时。APP都会在上传前缩小要上传的照片尺寸,以期更节省流量和时间。在HTML5时代,利用文件API和Canvas技术,Web上也可以做到图片压缩上传。

过滤文件类型

首先我们希望用户能直接选择手机照片,而不是在各种类型的文件中选择。只需要在input标签中加入accept属性就可以实现这一点:

<div id="preview"></div>
<form>
    <input type="file" accept="image/*">
    <input type="submit">
<form>

在Android4以上,iOS7以上设备实测,当用户点击这个文件选择器的时候,手机会自动调出图片库,并带有拍照选择。

PictureSelect

读文件生成预览

用户选择了图片之后,需要读取文件内容,读出的内容可供生成预览图,也可以供后面压缩使用。使用HTML5的FileReader API可以达成这一目的。

var file = document.querySelector("[type=file]");
file.addEventListener("change", function(e) {
    for (var i = 0, f; f = e.target.files[i]; i++) {
        if (f.type.indexOf("image") !== 0) continue;
        var reader = new FileReader();
        reader.onload = function(e) {
            var img = document.createElement("img");
            img.src = e.target.result;
            document.getElementById("preview").appendChild(img);
        }
        reader.readAsDataURL(f);
    }
}, false);

如果不需要预览图,可以不把img对象添加到DOM上。

压缩

利用Canvas渲染上下文的drawImage接口,可以把一张图片绘制到Canvas上,在这个过程中可以重新定义图片尺寸,然后再用Canvas的toDataURL接口可以生成出压缩后的图片。

var images = document.querySelectorAll("#preview img");
var dstWidth = 400, dstHeight = 300;
var compressedImages = [];
[].forEach.call(images, function (image) {
    var canvas = document.createElement("canvas");
    canvas.width = dstWidth;
    canvas.height = dstHeight;
    canvas.getContent("2d").drawImage(image); // 这里传入img元素对象
    var compressed = canvas.toDataURL("image/jpeg", 0.7);
    compressedImages.push(compressed);
});

上传

前面一步骤生成的压缩后图片是Data URL形式的,上传前需要把开头部分的data:image/jpeg;base64,截掉才是图片的Base64编码形式。

可以直接把Base64的字符串上传到服务器,然后由服务端解码为JPG图片,也可以在前端解码上传。如果要在前端解码并以文件方式上传,先要用atob函数把Base64解开,然后转换为ArrayBuffer,再用它创建一个Blob对象。文件方式上传需要用multipart/form-data格式,可以利用FormData对象组装生成好的Blob对象来实现。

function b64toBlob(b64Data, contentType, sliceSize) {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    var byteCharacters = atob(b64Data);
    var byteArrays = [];

    for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
        var slice = byteCharacters.slice(offset, offset + sliceSize);

        var byteNumbers = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            byteNumbers[i] = slice.charCodeAt(i);
        }

        var byteArray = new Uint8Array(byteNumbers);

        byteArrays.push(byteArray);
    }

    var blob = new Blob(byteArrays, {type: contentType});
    return blob;
}
var fileBlob = b64toBlob(compressed.substr(23), "image/jpeg");
var fd = new FormData();
fd.append("file", fileBlob);
var xhr = new XMLHttpRequest();
xhr.open("POST", "upload.php");
xhr.send(fd);

安全

最后,请不要忘记在服务端对用户上传的文件数据进行合法性检查,毕竟黑客也可能用非浏览器上传垃圾文件或者恶意脚本。

DEMO

您可以访问 https://xts.so/demo/compress/index.html 查看上传范例程序,或者拍摄下面的二维码:

3385794708.png

因为Android版的微信使用的是QQ浏览器的X5内核,而X5又是Webkit一个早期的分支版本(从UA上能看到是AppleWebKit/533),所以它并没有提供Blob构造器,也就无法使用new Blob()这样的语句,不过它包括了WebKitBlobBuilder,DEMO中实现了一个BlobConstructor来兼容微信,另外Webkit 534版以下的Chrome分支都存在FormData上传文件会变成0字节的问题,Andy EStackoverflow上提供了一个解决方案,我把它移植到DEMO里了。

DEMO程序的服务器端是PHP实现的,用于演示只有一行:

<?php var_dump($_FILES);?>

此DEMO没有实现图片等比缩放的逻辑。

SPDY代理省流技术的架构

基本架构

Opera Turbo技术让浏览器可以压缩浏览过程中非加密的图片内容,从而减少网络流量和提升加载速度(需要Turbo服务器的网络多线高度优化)。不过由于这一技术局限在浏览器中,并不方便使用。其实可以考虑基于OpenWRT实现一个类似的东西。

网络架构如下图所示:

1587764912.png

路由器上可以有一个程序嗅探并劫持明文HTTP流量,利用端口号和请求内容侦测把HTTP请求识别出来,通过SPDY通道发给Proxy Server处理。Proxy Server负责HTTP请求原始资源,利用其优质的网络连接和强大的服务器计算能力把请求回来的图片压缩缓存下来,并对客户端提供响应。

一些细节

当用户发送TCP SYN到80端口的时候就可以开始劫持流量,可能需要在内核模块上挂钩子完成任务,可以看看libcap和redsocks库是否可用。

和代理服务器间使用SPDY协议通信,保持长连接主要是为了得到它的多路复用的好处,少维护一点TCP连接,毕竟一般运行OpenWRT的设备内存都不大,服务器也可以从减少连接数中受益。

Proxy Server的前端使用SPDY,后端可以直接使用开源的HTTP代理程序加入压缩模块。如果不考虑复杂的认证(认证可以利用SPDY环节的TLS客户端证书功能完成),可以优先考虑基于polipo等轻量级的HTTP代理实现。

图片压缩环节可以考虑支持GPU加速的库,使用CUDA或者OpenCL实现,当然前提是压缩服务器上有显卡。可以将SPDY代理模块和HTTP压缩模块划分到不同的服务器硬件上组建集群。


以上只是基本架构的考虑,尚没有具体实现,之前计划基于Socks服务器实现一个原型,时间关系并没有完成。