=encoding utf-8


=head1 Name

lua-resty-rsa - RSA functions for OpenResty


=head1 Status

This library is considered production ready.

Build status: !L<https:E<sol>E<sol>github.comE<sol>spacewanderE<sol>lua-resty-rsaE<sol>workflowsE<sol>buildE<sol>badge.svg?branch=master|https://github.com/spacewander/lua-resty-rsa/actions/workflows/ci.yml/badge.svg?branch=master>


=head1 Description

This library requires an nginx build with OpenSSL,
the L<ngx_lua module|https://github.com/openresty/lua-nginx-module>, and L<LuaJIT|http://luajit.org/luajit.html>.


=head1 Synopsis


        # nginx.conf:
    
        lua_package_path "/path/to/lua-resty-rsa/lib/?.lua;;";
    
        server {
            location = /test {
                content_by_lua_file conf/test.lua;
            }
        }
    
        -- conf/test.lua:
    
        local resty_rsa = require "resty.rsa"
        local rsa_public_key, rsa_priv_key, err = resty_rsa:generate_rsa_keys(2048)
        if not rsa_public_key then
            ngx.say('generate rsa keys err: ', err)
        end
    
        ngx.say(rsa_public_key)
        --[[
        -----BEGIN RSA PUBLIC KEY-----
        MIIBCgKCAQEAuw4T755fepEyXTM66pzf6nv8NtnukQTMGnhmBFIFHp/P2vEpxjXU
        BBDUpzKkVFR3wuK9O1FNmRDAGNGYC0N/9cZNdhykA1NixJfKQzncN31VJTmNqJNZ
        W0x7H9ZGoh2aE0zCCZpRlC1Rf5rL0SVlBoQkn/n9LnYFwyLLIK5/d/y/NZVL6Z6L
        cyvga0zRajamLIjY0Dy/8YIwVV6kaSsHeRv2cOB03eam6gbhLGIz/l8wuJhIn1rO
        yJLQ36IOJymbbNmcC7+2hEQJP40qLvH7hZ1LaAkgQUHjfi8RvH2T1Jmce7XGPxCo
        Ed0yfeFz+pL1KeSWNey6cL3N5hJZE8EntQIDAQAB
        -----END RSA PUBLIC KEY-----
        ]]--
    
        ngx.say(rsa_priv_key)
        --[[
        -----BEGIN RSA PRIVATE KEY-----
        MIIEpAIBAAKCAQEAuw4T755fepEyXTM66pzf6nv8NtnukQTMGnhmBFIFHp/P2vEp
        xjXUBBDUpzKkVFR3wuK9O1FNmRDAGNGYC0N/9cZNdhykA1NixJfKQzncN31VJTmN
        qJNZW0x7H9ZGoh2aE0zCCZpRlC1Rf5rL0SVlBoQkn/n9LnYFwyLLIK5/d/y/NZVL
        6Z6Lcyvga0zRajamLIjY0Dy/8YIwVV6kaSsHeRv2cOB03eam6gbhLGIz/l8wuJhI
        n1rOyJLQ36IOJymbbNmcC7+2hEQJP40qLvH7hZ1LaAkgQUHjfi8RvH2T1Jmce7XG
        PxCoEd0yfeFz+pL1KeSWNey6cL3N5hJZE8EntQIDAQABAoIBAGim1ayIFK8EMQNH
        uDyui/Aqcc9WWky0PGTK23irUsXxb1708gQ89WNY70Cj6qBrqZ1VMb3QHPP4FSFN
        kh0rJJoi2g+ssm5R5r5KlhTKeFRrQInVC1Y3KhUUUwZa4aWtnhgSJ7Urq1yVhjU4
        K7PVkhH1OHBwcp/d1Bd6jd65AgPkY63P+WpcARJkClmQ1RhgoRwThyJdpKrV4/gO
        ha0AUGlJNRNvRwiZxP0zaI5C8RdrG96SnVpeYOcD0z/M1HVlkoYMXsXLKttwLfpK
        88Igtm6ZJwRpfuMF5VA+9hHaYGCBdGz0B/rMp2fc+EtrOavYQGrWIWi2RL1Qk6Rt
        BUyeTgECgYEA9anj4n/cak1MT+hbNFsL31mJXryl1eVNjEZj/iPMztpdS15CmFgj
        Kjr9UuintjSiK7Is43nZUWWyP1XQjRhVi2uP7PRIv92QNl/YteWD6tYCInJHKe2J
        QqYyZrElezsdayXb5DK6bi1UIYYji90g79N7x6pOR0UnQNQUXTv+Y8ECgYEAwuzl
        6Ez4BSXIIL9NK41jfNMa73Utfl5oO1f6mHM2KbILqaFE76PSgEeXDbOKdcjCbbqC
        KCGjwyPd+Clehg4vkYXTq1y2SQGHwfz7DilPSOxhPY9ND7lGbeNzDUK4x8xe52hd
        MWKdgqeqCK83e5D0ihzRiMah8dbxmlfLAOZ3sPUCgYEA0dT9Czg/YqUHq7FCReQG
        rg3iYgMsexjTNh/hxO97PqwRyBCJPWr7DlU4j5qdteobIsubv+kSEI6Ww7Ze3kWM
        u/tyAeleQlPTnD4d8rBKD0ogpJ+L3WpBNaaToldpNmr149GAktgpmXYqSEA1GIAW
        ZAL11UPIfOO6dYswobpevYECgYEApSosSODnCx2PbMgL8IpWMU+DNEF6sef2s8oB
        aam9zCi0HyCqE9AhLlb61D48ZT8eF/IAFVcjttauX3dWQ4rDna/iwgHF5yhnyuS8
        KayxJJ4+avYAmwEnfzdJpoPRpGI0TCovRQhFZI8C0Wb+QTJ7Mofmt9lvIUc64sff
        GD0wT/0CgYASMf708dmc5Bpzcis++EgMJVb0q+ORmWzSai1NB4bf3LsNS6suWNNU
        zj/JGtMaGvQo5vzGU4exNkhpQo8yUU5YbHlA8RCj7SYkmP78kCewEqxlx7dbcuj2
        LAPWpiDca8StTfEphoKEVfCPHaUk0MlBHR4lCrnAkEtz23vhZKWhFw==
        -----END RSA PRIVATE KEY-----
        ]]--
    
        local pub, err = resty_rsa:new({ public_key = rsa_public_key })
        if not pub then
            ngx.say("new rsa err: ", err)
            return
        end
        local encrypted, err = pub:encrypt("hello")
        if not encrypted then
            ngx.say("failed to encrypt: ", err)
            return
        end
        ngx.say("encrypted length: ", #encrypted)
    
        local priv, err = resty_rsa:new({ private_key = rsa_priv_key })
        if not priv then
            ngx.say("new rsa err: ", err)
            return
        end
        local decrypted = priv:decrypt(encrypted)
        ngx.say(decrypted == "hello")
    
        local algorithm = "SHA256"
        local priv, err = resty_rsa:new({ private_key = rsa_priv_key, algorithm = algorithm })
        if not priv then
            ngx.say("new rsa err: ", err)
            return
        end
    
        local str = "hello"
        local sig, err = priv:sign(str)
        if not sig then
            ngx.say("failed to sign:", err)
            return
        end
        ngx.say("sig length: ", #sig)
    
        local pub, err = resty_rsa:new({ public_key = rsa_public_key, algorithm = algorithm })
        if not pub then
            ngx.say("new rsa err: ", err)
            return
        end
        local verify, err = pub:verify(str, sig)
        if not verify then
            ngx.say("verify err: ", err)
            return
        end
        ngx.say(verify)


=head1 Methods

To load this library,


=over


=item 1.

you need to specify this library's path in ngx_lua's L<lua_package_path|https://github.com/openresty/lua-nginx-module#lua_package_path> directive. For example, C<lua_package_path "/path/to/lua-resty-rsa/lib/?.lua;;";>.

=item 2.

you use C<require> to load the library into a local Lua variable:


=back


        local rsa = require "resty.rsa"


=head2 generate_rsa_keys

C<syntax: public_key, private_key, err = rsa:generate_rsa_keys(bits, in_pkcs8_fmt)>

Generate rsa public key and private key by specifying the number of C<bits>.
The C<in_pkcs8_fmt> is optional. If C<in_pkcs8_fmt> is true, the generated priviate key is in PKCS#8 format and
the public key is in PKIX format, which start with C<-----BEGIN PUBLIC> or C<-----BEGIN PRIVATE>.
Otherwise the generated keys are in PKCS#1 format, which start with C<-----BEGIN RSA>.


=head2 new

C<syntax: obj, err = rsa:new(opts)>

Creates a new rsa object instance by specifying an options table C<opts>.

The options table accepts the following options:


=over


=item *

C<public_key>
Specifies the public rsa key.

=item *

C<private_key>
Specifies the private rsa key.

=item *

C<password>
Specifies the password to read rsa key.

=item *

C<key_type>
Specifies the type of given key.
By default the type will be detected from the value of the key.


=back

| C<key_type> value | meaning |
| ------------------- | ------ |
| rsa.KEY_TYPE.PKCS1 | The input key is in PKCS#1 format(usually starts with C<-----BEGIN RSA PUBLIC>). |
| rsa.KEY_TYPE.PKIX | The input key is in PKIX format(usually starts with C<-----BEGIN PUBLIC>). |


    -- creates a rsa object with PKIX format of public key
    local resty_rsa = require "resty.rsa"
    local pub, err = resty_rsa:new({
        public_key = RSA_PKCS8_PUB_KEY,
        key_type = resty_rsa.KEY_TYPE.PKIX,
    })
    
    -- creates a rsa object with pkcs#8 format of private key
    local priv, err = resty_rsa:new({
        private_key = RSA_PKCS8_PASS_PRIV_KEY,
        key_type = resty_rsa.KEY_TYPE.PKCS8,
        -- you need to specify the password if the pkey is encrypted
        -- password = "foobar",
    })


=over


=item *

C<padding>
Specifies the padding mode when you want to encrypt/decrypt.

=item *

C<algorithm>
Specifies the digest algorithm when you want to sign/verify.


=back

| C<algorithm> value | meaning |
| ------------------- | ------ |
| md4/MD4/RSA-MD4/md4WithRSAEncryption | digest with C<md4> |
| md5/MD5/RSA-MD5/md5WithRSAEncryption/ssl3-md5 | digest with C<md5> |
| ripemd160/RIPEMD160/RSA-RIPEM160/ripemd160WithRSA/rmd160 | digest with C<ripemd160> |
| sha1/SHA1/RSA-SHA1/sha1WithRSAEncryption/ssl3-sha1 | digest with C<sha1> |
| sha224/SHA224/RSA-SHA224/sha224WithRSAEncryption | digest with C<sha224> |
| sha256/SHA256/RSA-SHA256/sha256WithRSAEncryption | digest with C<sha256> |
| sha384/SHA384/RSA-SHA384/sha384WithRSAEncryption | digest with C<sha384> |
| sha512/SHA512/RSA-SHA512/sha512WithRSAEncryption | digest with C<sha512> |


=head2 encrypt

C<syntax: encrypted, err = obj:encrypt(str)>


=head2 decrypt

C<syntax: decrypted, err = obj:decrypt(encrypted)>


=head2 sign

C<syntax: signature, err = obj:sign(str)>


=head2 verify

C<syntax: ok, err = obj:verify(str, signature)>


=head1 Performance

I got the result:

    encrypt for 50000 times cost : 2.4110000133514s
    decrypt for 50000 times cost : 57.196000099182s
    sign for 50000 times cost : 59.169999837875s
    verify for 50000 times cost : 1.8230001926422s

when I run this script.

    local resty_rsa = require "resty.rsa"
    local algorithm = "SHA256"
    
    local rsa_public_key, rsa_priv_key, err = resty_rsa:generate_rsa_keys(2048)
    if not rsa_public_key then
        ngx.say("generate rsa keys err: ", err)
        return
    end
    
    local pub, err = resty_rsa:new({
        public_key = rsa_public_key,
        padding = resty_rsa.PADDING.RSA_PKCS1_PADDING,
        algorithm = algorithm,
    })
    if not pub then
        ngx.say("new rsa err: ", err)
        return
    end
    
    local priv, err = resty_rsa:new({
        private_key = rsa_priv_key,
        padding = resty_rsa.PADDING.RSA_PKCS1_PADDING,
        algorithm = algorithm,
    })
    if not priv then
        ngx.say("new rsa err: ", err)
        return
    end
    
    
    local num = 5 * 10000
    
    local str = "hello test"
    
    local encrypted, decrypted, err, sig, verify
    
    ngx.update_time()
    local now = ngx.now()
    
    local function timer(operation)
        ngx.update_time()
        local t = ngx.now()
    
        ngx.say(operation, " for ", num, " times cost : ", t - now, "s")
        now = t
    end
    
    for _ = 1, num do
        encrypted, err = pub:encrypt(str)
        if not encrypted then
            ngx.say("failed to encrypt: ", err)
            return
        end
    end
    
    timer("encrypt")
    
    for _ = 1, num do
        decrypted = priv:decrypt(encrypted)
        if decrypted ~= str then
            ngx.say("decrypted not match")
            return
        end
    end
    
    timer("decrypt")
    
    for _ = 1, num do
        sig, err = priv:sign(str)
        if not sig then
            ngx.say("failed to sign:", err)
            return
        end
    end
    
    timer("sign")
    
    for _ = 1, num do
        verify, err = pub:verify(str, sig)
        if not verify then
            ngx.say("verify err: ", err)
            return
        end
    end
    
    timer("verify")


=head1 Author

Dejiang Zhu (doujiang24) E<lt>doujiang24@gmail.comE<gt>

Zexuan Luo (spacewander)


=head1 Release Steps


=over


=item 1.

update the C<_VERSION> in C<lib/resty/rsa.lua>

=item 2.

update the C<version> in C<dist.ini>

=item 3.

rename current rockspec to the new version and update the reference in it.

=item 4.

tag the new version

=item 5.

opm upload


=back


=head1 Copyright and License

This module is licensed under the MIT license.

Copyright (C) 2014-2018, by Dejiang Zhu (doujiang24) E<lt>doujiang24@gmail.comE<gt>
Copyright (C) 2018-, by Zexuan Luo (spacewander)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:


=over


=item *

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.


=back


=over


=item *

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.


=back

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


=head1 See Also


=over


=item *

the ngx_lua module: http://wiki.nginx.org/HttpLuaModule

=item *

the lua-resty-string: https://github.com/openresty/lua-resty-string

=back

