Lua 中的 RSA 加解密实现
记得之前,部门某款游戏陆陆续续收到一些玩家反馈,抱怨在登录游戏时会等待很久。初步排查后基本断定可能是此游戏的登录服务器程序某块代码有问题,于是即安排了服务器同事作排查分析但一直无果。
之后我时间有了空余,开始协助排查调试。简单了解了此登录服务器的逻辑处理流程后(接收到经过加密的 HTTP 登陆请求-->解密数据包-->去数据库查询对应的玩家信息并作验证),一开始我简单认为瓶颈估计出现在“去数据库查询对应的玩家信息”这步,毕竟磁盘 IO 明显比较耗时,于是未作更多考虑,将每次登陆验证都去数据库查询的操作改为维护一个哈希表存储玩家信息,并在程序启动时载入近几日登陆过游戏的玩家信息至此哈希表中,且当从数据库查询出尚未缓存的玩家信息后即也存入哈希表以保证最多只有一次而非每次都要作磁盘 IO。本以为经过改进后的登录服务器已解决了问题,但内部压测表明,登陆耗时较长问题依旧存在,改进版本的登录服务器程序几无效果!
打脸之下,好奇心大起,于是再认真研究了登陆验证处理流程,之后分别在可能的瓶颈代码前后加上耗时打印(多么简单老土的做法:)),很快有了“重大”发现:处理 RSA 解密的代码,在 64 位四核 CentOS 上每次解密处理耗时约需 50ms 左右!
印象里非对称加解密比较慢,但如此不正常的慢,似乎有违常理,当时即用 Golang 撸了个一样的 RSA 解密程序,对比之下,耗时大大减少,于是很明显,基本可以断定,此游戏的登录服务器程序的 RSA 解密模块有较大的性能问题。
注:此游戏的服务器端程序基于 Pomelo 框架,完全由 Node.js 开发(也正因此,常见的性能分析等工具如 AQTime 等派不上用场)。
啰嗦了这么多,其实只想说两点:
- 可能吧,Node.js 虽然三方库大把,但不少库的质量恐怕...当然,网上似乎早已有类似告诫
- CPU 密集型操作,静态语言可能更适合(虽然譬如 Node.js,网络性能方面粗测很不错)
所以最近,趁着对 Lua 还有点印象,在参考研究了一些网上资源后,搞了套简单的在 Lua 中通过 ffi 方式调用由 C 实现的 RSA 加解密例程的方案,如下(编译后的 so 文件及源代码等压缩包可从这里下载)。
纯 C 实现的 RSA 加解密程序(通过 OpenSSL 调用):
/*******************************************************************************************
*
* Copyright (C) Ravishanker Kusuma / ecofast. All Rights Reserved.
*
* File: rsautils.c
* Date: 2017/12/01
* Desc: RSA Encryption & Decryption utils with OpenSSL in C
*
* Thks: http://hayageek.com/rsa-encryption-decryption-openssl-c/
*
* Compilation Command: gcc rsautils.c -fPIC -shared -lssl -lcrypto -o librsa.so
*******************************************************************************************/ #include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h> const int padding = RSA_PKCS1_OAEP_PADDING; int public_encrypt(unsigned char* data, int data_len, unsigned char* key, unsigned char* encrypted)
{
int ret = -;
BIO* keybio = BIO_new_mem_buf(key, -);
if (keybio != NULL)
{
RSA* rsa = NULL;
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
if (rsa != NULL)
{
ret = RSA_public_encrypt(data_len, data, encrypted, rsa, padding);
RSA_free(rsa);
}
BIO_free_all(keybio);
}
return ret;
} int private_decrypt(unsigned char* enc_data, int data_len, unsigned char* key, unsigned char* decrypted)
{
int ret = -;
BIO* keybio = BIO_new_mem_buf(key, -);
if (keybio != NULL)
{
RSA* rsa = NULL;
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
if (rsa != NULL)
{
ret = RSA_private_decrypt(data_len, enc_data, decrypted, rsa, padding);
RSA_free(rsa);
}
BIO_free_all(keybio);
}
return ret;
}
而 HTTP 传输,base64 编码自然少不了(源自 这里):
local function divide_string( str, max, fillChar )
fillChar = fillChar or ""
local result = {} local start =
for i = , #str do
if i % max == then
table.insert( result, str:sub( start, i ) )
start = i +
elseif i == #str then
table.insert( result, str:sub( start, i ) )
end
end return result
end local function number_to_bit( num, length )
local bits = {} while num > do
local rest = math.floor( math.fmod( num, ) )
table.insert( bits, rest )
num = ( num - rest ) /
end while #bits < length do
table.insert( bits, "" )
end return string.reverse( table.concat( bits ) )
end local function ignore_set( str, set )
if set then
str = str:gsub( "["..set.."]", "" )
end
return str
end local function pure_from_bit( str )
return ( str:gsub( '........', function ( cc )
return string.char( tonumber( cc, ) )
end ) )
end local function unexpected_char_error( str, pos )
local c = string.sub( str, pos, pos )
return string.format( "unexpected character at position %d: '%s'", pos, c )
end -------------------------------------------------------------------------------- local basexx = {} --------------------------------------------------------------------------------
-- base2(bitfield) decode and encode function
-------------------------------------------------------------------------------- local bitMap = { o = "", i = "", l = "" } function basexx.from_bit( str, ignore )
str = ignore_set( str, ignore )
str = string.lower( str )
str = str:gsub( '[ilo]', function( c ) return bitMap[ c ] end )
local pos = string.find( str, "[^01]" )
if pos then return nil, unexpected_char_error( str, pos ) end return pure_from_bit( str )
end function basexx.to_bit( str )
return ( str:gsub( '.', function ( c )
local byte = string.byte( c )
local bits = {}
for i = , do
table.insert( bits, byte % )
byte = math.floor( byte / )
end
return table.concat( bits ):reverse()
end ) )
end --------------------------------------------------------------------------------
-- base16(hex) decode and encode function
-------------------------------------------------------------------------------- function basexx.from_hex( str, ignore )
str = ignore_set( str, ignore )
local pos = string.find( str, "[^%x]" )
if pos then return nil, unexpected_char_error( str, pos ) end return ( str:gsub( '..', function ( cc )
return string.char( tonumber( cc, ) )
end ) )
end function basexx.to_hex( str )
return ( str:gsub( '.', function ( c )
return string.format('%02X', string.byte( c ) )
end ) )
end --------------------------------------------------------------------------------
-- generic function to decode and encode base32/base64
-------------------------------------------------------------------------------- local function from_basexx( str, alphabet, bits )
local result = {}
for i = , #str do
local c = string.sub( str, i, i )
if c ~= '=' then
local index = string.find( alphabet, c, , true )
if not index then
return nil, unexpected_char_error( str, i )
end
table.insert( result, number_to_bit( index - , bits ) )
end
end local value = table.concat( result )
local pad = #value %
return pure_from_bit( string.sub( value, , #value - pad ) )
end local function to_basexx( str, alphabet, bits, pad )
local bitString = basexx.to_bit( str ) local chunks = divide_string( bitString, bits )
local result = {}
for key,value in ipairs( chunks ) do
if ( #value < bits ) then
value = value .. string.rep( '', bits - #value )
end
local pos = tonumber( value, ) +
table.insert( result, alphabet:sub( pos, pos ) )
end table.insert( result, pad )
return table.concat( result )
end --------------------------------------------------------------------------------
-- rfc 3548: http://www.rfc-editor.org/rfc/rfc3548.txt
-------------------------------------------------------------------------------- local base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
local base32PadMap = { "", "======", "====", "===", "=" } function basexx.from_base32( str, ignore )
str = ignore_set( str, ignore )
return from_basexx( string.upper( str ), base32Alphabet, )
end function basexx.to_base32( str )
return to_basexx( str, base32Alphabet, , base32PadMap[ #str % + ] )
end --------------------------------------------------------------------------------
-- crockford: http://www.crockford.com/wrmg/base32.html
-------------------------------------------------------------------------------- local crockfordAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
local crockfordMap = { O = "", I = "", L = "" } function basexx.from_crockford( str, ignore )
str = ignore_set( str, ignore )
str = string.upper( str )
str = str:gsub( '[ILOU]', function( c ) return crockfordMap[ c ] end )
return from_basexx( str, crockfordAlphabet, )
end function basexx.to_crockford( str )
return to_basexx( str, crockfordAlphabet, , "" )
end --------------------------------------------------------------------------------
-- base64 decode and encode function
-------------------------------------------------------------------------------- local base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"..
"abcdefghijklmnopqrstuvwxyz"..
"0123456789+/"
local base64PadMap = { "", "==", "=" } function basexx.from_base64( str, ignore )
str = ignore_set( str, ignore )
return from_basexx( str, base64Alphabet, )
end function basexx.to_base64( str )
return to_basexx( str, base64Alphabet, , base64PadMap[ #str % + ] )
end --------------------------------------------------------------------------------
-- URL safe base64 decode and encode function
-------------------------------------------------------------------------------- local url64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"..
"abcdefghijklmnopqrstuvwxyz"..
"0123456789-_" function basexx.from_url64( str, ignore )
str = ignore_set( str, ignore )
return from_basexx( str, url64Alphabet, )
end function basexx.to_url64( str )
return to_basexx( str, url64Alphabet, , "" )
end --------------------------------------------------------------------------------
--
-------------------------------------------------------------------------------- local function length_error( len, d )
return string.format( "invalid length: %d - must be a multiple of %d", len, d )
end local z85Decoder = { 0x00, 0x44, 0x00, 0x54, 0x53, 0x52, 0x48, 0x00,
0x4B, 0x4C, 0x46, 0x41, 0x00, 0x3F, 0x3E, 0x45,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x40, 0x00, 0x49, 0x42, 0x4A, 0x47,
0x51, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x4D, 0x00, 0x4E, 0x43, 0x00,
0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
0x21, 0x22, 0x23, 0x4F, 0x00, 0x50, 0x00, 0x00 } function basexx.from_z85( str, ignore )
str = ignore_set( str, ignore )
if ( #str % ) ~= then
return nil, length_error( #str, )
end local result = {} local value =
for i = , #str do
local index = string.byte( str, i ) -
if index < or index >= #z85Decoder then
return nil, unexpected_char_error( str, i )
end
value = ( value * ) + z85Decoder[ index ]
if ( i % ) == then
local divisor = * *
while divisor ~= do
local b = math.floor( value / divisor ) %
table.insert( result, string.char( b ) )
divisor = math.floor( divisor / )
end
value =
end
end return table.concat( result )
end local z85Encoder = ""..
"abcdefghijklmnopqrstuvwxyz"..
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"..
".-:+=^!/*?&<>()[]{}@%$#" function basexx.to_z85( str )
if ( #str % ) ~= then
return nil, length_error( #str, )
end local result = {} local value =
for i = , #str do
local b = string.byte( str, i )
value = ( value * ) + b
if ( i % ) == then
local divisor = * * *
while divisor ~= do
local index = ( math.floor( value / divisor ) % ) +
table.insert( result, z85Encoder:sub( index, index ) )
divisor = math.floor( divisor / )
end
value =
end
end return table.concat( result )
end return basexx
兼容 LuaJIT 接口的 ffi 实现,源自 这里,我直接编译得到了 so 文件(见压缩包里的 ffi.so)。
然后是 rsautils.lua:
local ffi = require('ffi')
local rsa = ffi.load('./librsa')
local basexx = require('basexx') local _M = {} ffi.cdef[[
int public_encrypt(unsigned char * data,int data_len,unsigned char * key, unsigned char *encrypted);
int private_decrypt(unsigned char * enc_data,int data_len,unsigned char * key, unsigned char *decrypted); int private_encrypt(unsigned char * data,int data_len,unsigned char * key, unsigned char *encrypted);
int public_decrypt(unsigned char * enc_data,int data_len,unsigned char * key, unsigned char *decrypted);
]] local RSA_PUBLIC_KEY = [[-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3bTBJNQJjY6u7Y5b2eOWws0yW
CGuWPm6MGOSVan65wCrJa5p3q3sodQUDVPotjsknjLlje9E1F7Bx94ZuqTwkvAr6
ieLkgbbeqTCzeJ0AryUXiF3auxFSPdpBoD6nxtEeN8bZwfa/IYzdKyKlbhiQbUMN
qWgmxiPVwupwAML7RQIDAQAB
-----END PUBLIC KEY-----]] local RSA_PRIV_KEY = [[-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC3bTBJNQJjY6u7Y5b2eOWws0yWCGuWPm6MGOSVan65wCrJa5p3
q3sodQUDVPotjsknjLlje9E1F7Bx94ZuqTwkvAr6ieLkgbbeqTCzeJ0AryUXiF3a
uxFSPdpBoD6nxtEeN8bZwfa/IYzdKyKlbhiQbUMNqWgmxiPVwupwAML7RQIDAQAB
AoGAc4NXvUKc1mqWY9Q75cwNGlJQEMwMtPlsNN4YVeBTHjdeuqoBBQwA62GGXqrN
QpOBKl79ASGghob8n0j6aAY70PQqHSU4c06c7UlkeEvxJKlyUTO2UgnjjIHb2flR
uW8y3xmjpXAfwe50eAVMNhCon7DBc+XMF/paSVwiG8V+GAECQQDvosVLImUkIXGf
I2AJ2iSOUF8W1UZ5Ru68E8hJoVkNehk14RUFzTkwhoPHYDseZyEhSunFQbXCotlL
Ar5+O+lBAkEAw/PJXvi3S557ihDjYjKnj/2XtIa1GwBJxOliVM4eVjfRX15OXPR2
6shID4ZNWfkWN3fjVm4CnUS41+bzHNctBQJAGCeiF3a6FzA/0bixH40bjjTPwO9y
kRrzSYX89F8NKOybyfCMO+95ykhk1B4BF4lxr3drpPSAq8Paf1MhfHvxgQJBAJUB
0WNy5o+OWItJBGAr/Ne2E6KnvRhnQ7GFd8zdYJxXndNTt2tgSv2Gh6WmjzOYApjz
heC3jy1gkN89NCn+RrECQBTvoqFHfyAlwOGC9ulcAcQDqj/EgCRVkVe1IsQZenAe
rKCWlUaeIKeVkRz/wzb1zy9AVsPC7Zbnf4nrOxJ23mI=
-----END RSA PRIVATE KEY-----]] function _M.rsa_encrypt(plainText)
local c_str = ffi.new("char[?]", / )
ffi.copy(c_str, plainText)
local pub = ffi.new("char[?]", #RSA_PUBLIC_KEY)
ffi.copy(pub, RSA_PUBLIC_KEY)
local cipherText = ffi.new("char[?]", )
local cipherLen = rsa.public_encrypt(c_str, #plainText, pub, cipherText)
if cipherLen == - then
return -, nil
end
return cipherLen, basexx.to_base64(ffi.string(cipherText, cipherLen))
end function _M.rsa_decrypt(cipherLen, b64cipherText)
local c_str = ffi.new("char[?]", cipherLen + )
ffi.copy(c_str, basexx.from_base64(b64cipherText))
local pri = ffi.new("char[?]", #RSA_PRIV_KEY)
ffi.copy(pri, RSA_PRIV_KEY)
local plainText = ffi.new("char[?]", )
local plainLen = rsa.private_decrypt(c_str, cipherLen, pri, plainText)
if plainLen == - then
return nil
end
return ffi.string(plainText, plainLen)
end return _M
简单测试:
local rsautils = require('rsautils') local src_str = "my name is ecofast小0胡!!"
local cipherLen, cipher = rsautils.rsa_encrypt(src_str)
if cipherLen ~= - then
local plain = rsautils.rsa_decrypt(cipherLen, cipher)
print("src text:", plain) print("=========================")
local txt2 = rsautils.rsa_decrypt(, "aeMIl3wyPP/DIJLudq49k1YeK9o6QhrScyjy2JHcJ7CmFOpQAmbwLxOe/rWigSYeWbAMUw2MB1KTIsool9zEuOSaoiZtgjfpDvf5g/MZUjPAmDofKVutG9xJNonVoK6usHKVcR7wozq/tJ8h/CUWyKGHnLgkxvU3ObbhLPm/wwI=")
print("src text2:", txt2)
end
后记:或许在某个合适的时候,再写一篇关于(大规模)使用脚本如 Lua、Node.js 等作(游戏)开发的思考,或反思吧。个人而言,非常不倾向较大规模地使用脚本语言作开发。至少,用合适的工具做合适的事——而我们都知道,脚本语言,又名胶水语言。
Lua 中的 RSA 加解密实现的更多相关文章
- C# 中使用 RSA加解密算法
一.什么是RSA RSA公开密钥密码体制.所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制. 在公开密钥密码体制中,加密密钥(即 ...
- Java中的RSA加解密工具类:RSAUtils
本人手写已测试,大家可以参考使用 package com.mirana.frame.utils.encrypt; import com.mirana.frame.utils.log.LogUtils; ...
- VUE+webpack+npm项目中的RSA加解密
一.安装jsencrypt npm i jsencrypt node_modules文件夹中出现jsencrypt 二.引入jsencrypt 在main.js中import: import JsEn ...
- Vue项目中的RSA加解密
前后端使用rsa加密: 一般是客户端初始化时访问登录服务时,服务端面动态生成一对RSA对,公钥传给客户端,客户端拿到后,用户输入密码后,点登录时用公钥加密返回给服务端,服务端用私钥解就行了 一.安装 ...
- 在SQL SERVER中实现RSA加解密函数(第二版)
/*************************************************** 作者:herowang(让你望见影子的墙) 日期:2010.1.5 注: 转载请保留此信息 更 ...
- 在SQL SERVER中实现RSA加解密函数(第一版)
/*************************************************** 作者:herowang(让你望见影子的墙) 日期:2010.1.1 注: 转载请保留此信息 ...
- Rsa加解密Java、C#、php通用代码 密钥转换工具
之前发了一篇"TripleDes的加解密Java.C#.php通用代码",后面又有项目用到了Rsa加解密,还是在不同系统之间进行交互,Rsa在不同语言的密钥格式不一样,所以过程中主 ...
- 【go语言】RSA加解密
关于go语言的RSA加解密的介绍,这里有一篇文章,已经介绍的很完整了. 对应的go语言的加解密代码,参考git. 因为原文跨语言是跟php,我这里要跟c语言进行交互,所以,这里贴上c语言的例子. 参考 ...
- java RSA加解密以及用途
在公司当前版本的中间件通信框架中,为了防止非授权第三方和到期客户端的连接,我们通过AES和RSA两种方式的加解密策略进行认证.对于非对称RSA加解密,因为其性能耗费较大,一般仅用于认证连接,不会用于每 ...
随机推荐
- 推荐一款能支持国密SM2浏览器——密信浏览器
密信浏览器( MeSince Browser )是基于Chromium开源项目开发的国密安全浏览器,支持国密算法和国密SSL证书,同时也支持国际算法及全球信任SSL证书:密信浏览器使用界面清新,干净. ...
- 【BZOJ3065】带插入区间k小值
题意: 带插入,修改的区间k小值在线查询 原序列长度<=35000,插入个数<=35000,修改个数<=70000,0<=权值<=70000 题解: Orz vfleak ...
- Project Euler 25 1000-digit Fibonacci number
题意:在斐波那契数列( 1 ,1,2,3,5 ...... )中,第一个有1000位数字的是第几项? 思路:滚动数组 + 大数加法 /********************************* ...
- MPlayer 开始支持RTSP/RTP流媒体文件
hostzhu点评:MPlayer对流媒体的支持,让大家能更进一步地利用linux来看网络直播,对Linux下多媒体应用的推动作用可以说不可度量. RTSP/RTP streaming support ...
- mysql 插入更新在一条sql ON DUPLICATE KEY UPDATE
有时候需要进行数据操作的,如果有数据则更新数据, 没有数据则插入. 以往的做法是先查询,再根据查询结果进行判断,执行插入或更新操作 其实 有一种 ON DUPLICATE KEY UPDATE 语法, ...
- centos安装wget 及配置
yum -y install wget #yum -y install setup 本文 #yum install perl Searching for GCC... The path "& ...
- [Beginning SharePoint Designer 2010]Chapter4 发布页面
本章概要: 1.SharePoint中的Web内容管理 2.SharePoint发布系统的特性 3.SharePoint发布页面的组成 4.母板页 5.如何构建页面布局和他们潜在的内容类型
- 【LeetCode-面试算法经典-Java实现】【033-Search in Rotated Sorted Array(在旋转数组中搜索)】
[033-Search in Rotated Sorted Array(在旋转数组中搜索)] [LeetCode-面试算法经典-Java实现][全部题目文件夹索引] 原题 Suppose a sort ...
- 遗传奥秘的伟大揭秘者:J.Watson
J.Watson的近照: 人们公认,揭秘生命体的遗传奥秘(DNA)是二十世纪最伟大的科技成果之中的一个,或许就是人类最伟大的科技进步(而不是"之中的一个"). 上世纪是人类做出伟大 ...
- python的range()函数使用方法
python的range()函数使用非常方便.它能返回一系列连续添加的整数,它的工作方式类似于分片.能够生成一个列表对象. range函数大多数时常出如今for循环中.在for循环中可做为索引使用.事 ...