在 Node.js 中使用 SQLCipher

经过试验, 使用 Electron 打包之后我们实现的加密代码并没有打入. 经过排查, 发现是由于在 Electron 重新编译代码的过程中, 从服务器端下载了已经官方提供的编译好的工具, 并没有使用我们自己的工具. 因此这里需要自己对 Node-SQLite 代码做出修改. 此外, 由于尽量少的采用 dll 的原则, 因此对于 SQLCipher 我们采用了静态库链接的方式. 修改后的 Node-Sqlite3

命令行安装

网络上查到的命令安装方式:

在 Windows 上执行以下命令

npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=yourlib --verbose

在 Mac 上执行以下命令

export LDFLAGS="-L/yourlib"
export CPPFLAGS="-I/yourlib/include -I/yourlib/include/sqlcipher -I/yourlib/include/openssl" 
export CXXFLAGS="-I/yourlib/include -I/yourlib/include/sqlcipher -I/yourlib/include/openssl" 
npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --openssl_libname=crypto --sqlite=yourlib --verbose

这个时候, 在本机上你就已经根据你的 sqlcipher 安装好了一份加密的 sqlite3. 但是这里有个问题, 所有的库全部都是动态库!. 由于我们不想使用 dll ,因此需要对 Node-Sqlite3 进行修改. 这个修改会在会面做出详解.

修改后执行以下命令, sqlcipher 就安装好了.

npm install git+https://github.com/chinaofmelon/node-sqlite3.git --build-from-source --verbose
  • 当 lib 中存在有动态库的时候, 默认会安装动态库. 但是我们需要连接静态库. 这时候删除动态库就可以了. 但是 sqlcipher 的静态库依赖于 openssl 的静态库, 因此需要将 openssl 的库也放到 yourlib 中, 并指定名称.

  • 由于默认的 node-sqlite3 只有一个参数, 因此我对其做了修改, 增加了一个 openssl_lib 的参数适应静态编译过程.

  • 使用 package.json 安装

    但是大部分的时候, 我们都是很多开发人员共同开发. 那么一般情况下, 就是在工程中创建一个 package.json 文件, 然后开发人员在各自的环境中进行安装各个模块. 这时候上面的命令就用不了了. 我的做法是, 在 git 仓库上自建一个 npm package. 在这个 package 内部, 根据平台不同执行不同的编译命令, 以提供在不同的平台上使用.

    package.json

    "dependencies": { "sqlite3": "git+https://github.com/chinaofmelon/node-sqlite3.git",

    postinstall.js

    require('shelljs/global');
    var isArray = require('util').isArray;
    var args;
    try {
        args = JSON.parse(process.env.npm_config_argv).original
    } finally {
        if (!isArray(args)) {
            args = [];
    var targetArgs = args.filter(function (arg) {
        return /^--(runtime|target)/.test(arg);
    var targetStr = targetArgs.reduce(function (m, arg) {
        return m + ' ' + arg;
    }, '');
    if (process.platform == 'win32') {
        // WINDOWS
        exec("cd node_modules\\sqlite3 && npm install --build-from-source");
    } else if (process.platform === 'darwin') {
        // MAC
        exec("cd node_modules/sqlite3 && npm install --build-from-source");
    } else {
        // linux
        console.warn("[AKSQLiteCipher] Do not support linux OS yet");
        exit(1);
    

    至此, 执行 npm install, SQLCipher 就已经安装好了.

    使用 js 代码进行测试:

    // node test-sqlcipher-fts.js
    'use strict';
    var sqlite3 = require('你的名字');
    var db = new sqlite3.Database('./test.sqlcipher');
    db.serialize(function() {
      var stmt
        , messages
      db.run("PRAGMA KEY = 'secret'");
      // db.run("PRAGMA key = \"x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'\"");
    //   db.run("PRAGMA CIPHER = 'aes-128-cbc'");
      db.run("CREATE TABLE messages(id INTEGER, user VARCHAR, msg TEXT)");
      db.run("CREATE VIRTUAL TABLE messages_fts USING FTS4(user VARCHAR, msg TEXT)");
      stmt = db.prepare("INSERT INTO messages(id, user, msg) VALUES (?, ?, ?)");
      messages = [
        [1, 'coolaj86', 'this is test message number one']
      , [2, 'ajthedj', 'this is test message number two']
      , [3, 'coolaj86', 'this is test message number three']
      messages.forEach(function (msg) {
        stmt.run(msg);
      stmt.finalize();
      db.run("INSERT INTO messages_fts SELECT user, msg FROM messages");
      db.get("SELECT * FROM messages INNER JOIN messages_fts ON messages.user = messages_fts.user WHERE messages_fts.msg MATCH 'one'", function (err, data) {
        if (err) {
          console.error(err);
          return;
        console.log(data);
      db.all("SELECT * FROM messages INNER JOIN messages_fts ON messages.user = messages_fts.user WHERE messages_fts.msg MATCH 'two'", function (err, data) {
        if (err) {
          console.error(err);
          return;
        console.log(data);
      db.each("SELECT * FROM messages INNER JOIN messages_fts ON messages.user = messages_fts.user WHERE messages_fts.msg MATCH 'message'", function (err, data) {
        if (err) {
          console.error(err);
          return;
        console.log(data);
    

    执行 node test-sqlcipher-fts.js. 如果出现以下画面, 就说明编译成功了.

    $ node test-sqlcipher-fts.js
    { id: 1,
      user: 'coolaj86',
      msg: 'this is test message number one' }
    [ { id: 2,
        user: 'ajthedj',
        msg: 'this is test message number two' } ]
    { id: 1,
      user: 'coolaj86',
      msg: 'this is test message number one' }
    { id: 1,
      user: 'coolaj86',
      msg: 'this is test message number three' }
    { id: 2,
      user: 'ajthedj',
      msg: 'this is test message number two' }
    { id: 3,
      user: 'coolaj86',
      msg: 'this is test message number one' }
    { id: 3,
      user: 'coolaj86',
      msg: 'this is test message number three' }
    $ hexdump -C test.sqlcipher | head -15
    00000000  8f 47 28 8d cc 91 72 15  8c fd c6 11 57 41 99 16  |.G(...r.....WA..|
    00000010  bf 15 20 d4 65 b1 17 1c  73 30 ae 43 fd 31 9e 0c  |.. .e...s0.C.1..|
    00000020  c3 5c dc e1 a6 2e 80 b1  3b 97 d9 ed fe dc ea f3  |.\......;.......|
    00000030  a8 de ad 04 4b 73 cf ad  01 74 f0 c7 19 71 d3 07  |....Ks...t...q..|
    00000040  a3 4f fa 88 ce 00 f1 53  15 dd 06 1b e0 e7 94 50  |.O.....S.......P|
    00000050  dd 44 a5 8e d9 21 0d 86  f1 7c 37 7e a2 c1 ce a4  |.D...!...|7~....|
    00000060  6e d5 54 c3 79 67 0f dd  1c 0f 3a ac c7 1f ad b3  |n.T.yg....:.....|
    00000070  75 63 e7 88 d1 9b f3 f0  16 f3 58 6b 5a 59 b1 70  |uc........XkZY.p|
    00000080  c1 77 30 29 fa b4 ef 42  f5 88 57 3f 4c a9 e8 1b  |.w0)...B..W?L...|
    00000090  ba d3 aa cb b4 40 a1 0b  53 23 48 92 d1 25 a7 7a  |.....@..S#H..%.z|
    000000a0  60 ea 65 b0 56 ea 25 7f  6e cb 74 c5 71 a4 83 df  |`.e.V.%.n.t.q...|
    000000b0  52 83 a5 82 0e f7 6b 0f  33 85 16 85 9b 08 2e 93  |R.....k.3.......|
    000000c0  23 e0 75 bc 09 bf 76 ca  9a cf e6 8d bf 65 82 0b  |#.u...v......e..|
    000000d0  de 68 4e d9 77 0b 5b d4  23 b2 b9 56 7e c5 5c a5  |.hN.w.[.#..V~.\.|
    000000e0  c7 a5 75 cc 9b 85 f4 64  ca 47 f9 e3 54 ca 20 c6  |..u....d.G..T. .|
    

    这个时候就可以看出来, 数据库已经被加密了.

    LNK2001 无法解析的外部符号 imp _endthreadex

    缺少了 Windows 多线程的库 msvcrt.lib

    LNK2001 无法解析的外部符号 __imp__CertOpenStore@20

    因为 OpenSSL 使用了 Windows 的一个加密库: crypt32ws2_32. 加上就可以了.