Menu

Difficulties with encrypted RSA private keys

Help
2011-03-02
2012-12-11
  • Jonah (pidder)

    Jonah (pidder) - 2011-03-02

    Copied from our pidder support forum:

    I'm working on a project for school and we are attempting to make use of encrypted private RSA keys in javascript. However, the example code in https://www.pidder.com/pidcrypt/?page=rsa isn't working for me. I generated a my key using the following command:

    openssl genrsa -aes256
    

    I gave it the passphrase "testing" and wound up with the following encrypted key (obviously this is a key created for this purpose, so there's no problem with me posting it here):

    -----BEGIN RSA PRIVATE KEY-----
    Proc-Type: 4,ENCRYPTED
    DEK-Info: AES-256-CBC,8CFED9A4C90B25317A4EC66561C510D9
    yyWMc56wVUa5NQ7b36Ccc0X3gvB4ikcvn5z0EV63teLLqRRBnnOUxB2QH0DX6uRs
    L6eXtbdAD5DI6GhhhyWARkEciqwp5qksAceyowPD8dqXUxsFGb3t7/NjGCNHkItN
    jehtFgGq8MSFMcY1C82/N0A5OhNcu2TAbockaL7pQN5j2RWUx92DMZoSIY88ESn4
    ol/bpQDozw0YSla7BMlInxxzyiGKxzXCUKlhMahafZzHqFeLTz3bA93vFb717XPp
    QZH4q8umgex0CgGaWYn6LJGL1xD/GDfuuHycL/5P0U0Dnl04Eajh/oFGsMy3oH2U
    c3X1Hm+guiH2NYzMIfcCjEhKT0tcNtg6gfu8cwuK5A3MiJTuqDfxrSr39Z2xiExe
    tqbPDR+czULeZGmYn4GdsGHxokIEfW50+WGtCRndbhs=
    -----END RSA PRIVATE KEY-----
    

    I'm using the following code (more or less copied from the example):

    var pem = "yyWMc56wVUa5NQ7b36Ccc0X3gvB4ikcvn5z0E (you get the point) xokIEfW50+WGtCRndbhs=";
    
    function test() {
      var aes = new pidCrypt.AES.CBC();
      //-----BEGIN RSA PRIVATE KEY-----
      //Proc-Type: 4,ENCRYPTED
      //DEK-Info: AES-256-CBC,3E54850C520D5DE0551F30669F9330A5
      //              ^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      //              |||     |||||||||||||||||||pem.iv|||||||
      //              |||     ||||pem.salt||||
      //              pem.bits
      var salt = pidCryptUtil.convertFromHex("8CFED9A4C90B2531".toLowerCase());
      var k_and_iv = aes.createKeyAndIv(
        {
          password: "testing",
          salt: salt,
          bits: 256
        }
      );
      //initialize aes-cbc by values for cert decryption (OpenSSL aes-cbc
      //standard enc adds a byte 10 (A0) before byte pad, while cert 
      //aes-cbc crypt seems not to => A0_PAD = false, also cert b64
      //decoded string is an octet string and is not UTF-8 encoded
      //=> UTF8 = false)
      aes.initByValues(
        pem,
        k_and_iv.key,
        "8CFED9A4C90B25317A4EC66561C510D9".toLowerCase(),
        {
          UTF8: false,
          A0_PAD: false,
          nBits: 256
        }
      );
      //AES decryption
      var rsapem_decrypted = aes.decrypt();
      //rsapem_decrypted can be used for ASN.1 parsing
      $("#debug").html(aes.getAllMessages({}));
      console.log(pidCryptUtil.encodeBase64(rsapem_decrypted));
    }
    

    Unfortunately, the result is basically gibberish. It's really a big pain in the butt to debug this because I can't find anything that says exactly how the passphrase is used to generate the key. There is a URL contained in the comments for aes.createKeyAndIv(…), but it's a broken link :-(.

    Furthermore, ideally I would like this encryption to be very secure. For instance, users of our project may store their encrypted private key in a semi-trusted location. So if private keys get leaked in their encrypted form, we'd like them to be more or less secure. The code for aes.createKeyAndIv(…) seems to just do a couple rounds of salt + MD5, which doesn't seem too secure to me (rainbow table generation time would be very low). So if that algorithm is accurate, it would seem that maybe a stronger key derivation function might be necessary (e.g. PBKDF2?). Of course, a customized KDF kills my openssl compatibility (at least makes it more involved), but I'd rather see the private keys be secure and annoying than insecure and easy. :-)

    Basically, what I'm looking for here is information on where I might have gone wrong and, with luck, a pointer to where I might find more information on the algorithm(s) used in OpenSSL's RSA private key encryption mechanism, specifically the key derivation function used.

    PS - Please forgive me if I said anything really stupid there. I'm very new to cryptography.
    PPS - I'm using the following version of OpenSSL (in case it matters): OpenSSL 1.0.0c-fips 2 Dec 2010

    • pete
     
  • Jonah (pidder)

    Jonah (pidder) - 2011-03-02

    Unfortunately, the result is basically gibberish.

    Well, in your example you converted the salt from hex whereas createKeyAndIv() needs the salt in
    hex representation, so don't convert!

    The result of that will be an ASN.1 structure which you'll have to parse in order to get to the private key properties (see https://www.pidder.com/pidcrypt/?page=asn1).

    So a working modification of your code example looks like this:

    function test() {
      var aes = new pidCrypt.AES.CBC();
      //-----BEGIN RSA PRIVATE KEY-----
      //Proc-Type: 4,ENCRYPTED
      //DEK-Info: AES-256-CBC,8CFED9A4C90B25317A4EC66561C510D9
      //              ^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      //              |||     |||||||||||||||||||iv|||||||||||
      //              |||     ||||salt||||
      //              bits
      //
      //yyWMc56wVUa5NQ7b36Ccc0X (you get the point) WGtCRndbhs=
      //|||||||||||||||||||||||||||||||||||||||||||||||||||||||
      //b64
      var bits = 256;
      //createKeyAndIv needs the salt in hex, so don't convert!
      var salt = "8CFED9A4C90B2531".toLowerCase();
      var iv = "8CFED9A4C90B25317A4EC66561C510D9".toLowerCase();
      var b64 = "yyWMc56wVUa5NQ7b36Ccc0X3gvB4ikcvn5z0EV63teLLqRRBnnO\
    UxB2QH0DX6uRsL6eXtbdAD5DI6GhhhyWARkEciqwp5qksAceyowPD8dqXUxsFGb3t7/NjGC\
    NHkItNjehtFgGq8MSFMcY1C82/N0A5OhNcu2TAbockaL7pQN5j2RWUx92DMZoSIY88ESn4o\
    l/bpQDozw0YSla7BMlInxxzyiGKxzXCUKlhMahafZzHqFeLTz3bA93vFb717XPpQZH4q8um\
    gex0CgGaWYn6LJGL1xD/GDfuuHycL/5P0U0Dnl04Eajh/oFGsMy3oH2Uc3X1Hm+guiH2NYz\
    MIfcCjEhKT0tcNtg6gfu8cwuK5A3MiJTuqDfxrSr39Z2xiExetqbPDR+czULeZGmYn4GdsG\
    HxokIEfW50+WGtCRndbhs=";
      //you can find a certificate parsing function certParser(certificate)
      //in the RSA certificate parsing demo at 
      //https://www.pidder.com/pidcrypt/?page=demo_rsa-certificate 
      //to extract bits, salt and iv from your certificate rather than 
      //setting the values manually.
      var k_and_iv = aes.createKeyAndIv(
        {
           password: "testing",
           salt: salt,
           bits: bits
        }
      );
      //initialize aes-cbc by values for cert decryption (OpenSSL aes-cbc
      //standard enc adds a byte 10 (A0) before byte pad, while cert
      //aes-cbc crypt seems not to => A0_PAD = false, also cert b64
      //decoded string is an octet string and is not UTF-8 encoded
      //=> UTF8 = false)
      aes.initByValues(
        b64,
        k_and_iv.key,
        iv,
        {
           UTF8: false,
           A0_PAD: false,
           nBits: bits
        }
      );
      //AES decryption
      var rsapemDecrypted = aes.decrypt();
      
      //ASN decoding function needs a byte array
      var rsapemDecryptedBytes = pidCryptUtil.toByteArray(rsapemDecrypted);
      
      //rsapemDecryptedBytes can be used for ASN.1 parsing
      var asn = pidCrypt.ASN1.decode(rsapemDecryptedBytes);
      
      //asn.toHexTree() returns the parameters used for RSA intialization
      //@returns node: as javascript object tree with hex strings as values
      //e.g. RSA Public Key gives
      // {
      //   type: SEQUENCE,
      //   sub: [           
      //          {type: 'INTEGER', value: version},
      //          {type: 'INTEGER', value: modulus},
      //          {type: 'INTEGER', value: public exponent},
      //          {type: 'INTEGER', value: private exponent},
      //          {type: 'INTEGER', value: prime1},
      //          {type: 'INTEGER', value: prime2},
      //          {type: 'INTEGER', value: exponent1},
      //          {type: 'INTEGER', value: exponent2},
      //        ]
      // }
      var tree = asn.toHexTree();
      
      //initializing RSA module
      var rsa = new pidCrypt.RSA();
      
      //set RSA parameters from ASN.1 structure
      rsa.setPrivateKeyFromASN(tree);
      
      //RSA encryption
      var encryptedText = rsa.encrypt('Hello World');
      alert(encryptedText);
      
      //RSA decryption
      var decryptedText = rsa.decrypt(encryptedText);
      alert(decryptedText);
    }
    

    There is a URL contained in the comments for aes.createKeyAndIv(…), but it's a broken link

    Sorry about that, it seems the forum covering the topcic that we linked to is gone.
    However, a working link is http://www.jensign.com/JavaScience/dotnet/DeriveKeyM/

    So if that algorithm is accurate, it would seem that maybe a stronger key derivation function might be necessary

    OpenSSL is one of the most popular tools for cryptographic funtions, and their project is very well maintained. That is also the reason why we decided to make our library compatible.

    So rather than trying to implement something that breaks compatibility with OpenSSL we advise getting involved with that project if you think something should be improved - but that's beyond the scope of our little project :)

     
  • Pidder

    Pidder - 2011-03-02
    //e.g. RSA Public Key gives
    

    should read

    //e.g. RSA Private Key gives
    
     
  • Pete

    Pete - 2011-03-02

    Thank you for the fast reply! I will test this out as soon as I get a chance It works like a charm! You should probably change the example code on the website, though, as it implies you do need to convert from hex to a byte array:

    //-----BEGIN RSA PRIVATE KEY-----
    //Proc-Type: 4,ENCRYPTED
    //DEK-Info: AES-256-CBC,3E54850C520D5DE0551F30669F9330A5
    //              ^^^     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //              |||     |||||||||||||||||||pem.iv|||||||
    //              |||     ||||pem.salt||||
    //              pem.bits
    var salt = pidCryptUtil.convertFromHex(pem.salt);
    var k_and_iv = aes.createKeyAndIv(
         {
              password: password,
              salt: salt,
              bits: parseInt(pem.bits)
         }
    );
    

    Thanks, again!

    Your library is great!

     
  • Pidder

    Pidder - 2011-03-02

    Ooops!  That example was outdated. The salt is now converted within createKeyAndIv(params). I have changed the example code.

     
  • Jonah (pidder)

    Jonah (pidder) - 2011-03-02

    So it was our fault all along … ;)

     
  • Pete

    Pete - 2011-03-02

    So it was our fault all along … ;)

    No worries. It gave me a good excuse to spend about 3 hours last night researching the KDF used by OpenSSL. I'm still not 100% convinced that the KDF is secure enough for our ultimate needs, but it should be good enough for this project. If I continue work on the project after this semester, I'll probably do more research on the subject.

    I don't think it's a matter of poor security in OpenSSL. I imagine it was a decision between high strength encryption and compliance with export laws (stupid export laws - ugh) and legacy compatibility (older systems may not be able to handle high strength KDFs in a timely fashion). As long as you're using a high strength passphrase, pretty much any KDF should be fine. But since our project targets "normal" users who might use weak passwords, a stronger KDF would offer those users more security. So essentially, it's not that OpenSSL is doing anything "wrong," it's just that they designed this system with different priorities in mind.

    Whatever the case, it's been far too long since I took the kind of math classes I need to understand this stuff properly. I fear that I'm going to find myself back in a math classroom before I finish this degree… ;-)

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.