text: /*! (c) Tom Wu | http://www-cs-students.stanford.edu/~tjw/jsbn/
   */
  // Depends on rsa.js and jsbn2.js
  
  // Version 1.1: support utf-8 decoding in pkcs1unpad2
  
  // Undo PKCS#1 (type 2, random) padding and, if valid, return the plaintext
  function pkcs1unpad2(d,n) {
    var b = d.toByteArray();
    var i = 0;
    while(i < b.length && b[i] == 0) ++i;
    if(b.length-i != n-1 || b[i] != 2)
      return null;
    ++i;
    while(b[i] != 0)
      if(++i >= b.length) return null;
    var ret = "";
    while(++i < b.length) {
      var c = b[i] & 255;
      if(c < 128) { // utf-8 decode
        ret += String.fromCharCode(c);
      }
      else if((c > 191) && (c < 224)) {
        ret += String.fromCharCode(((c & 31) << 6) | (b[i+1] & 63));
        ++i;
      }
      else {
        ret += String.fromCharCode(((c & 15) << 12) | ((b[i+1] & 63) << 6) | (b[i+2] & 63));
        i += 2;
      }
    }
    return ret;
  }
  
  // PKCS#1 (OAEP) mask generation function
  function oaep_mgf1_str(seed, len, hash)
  {
      var mask = '', i = 0;
  
      while (mask.length < len)
      {
          mask += hash(seed + String.fromCharCode.apply(String, [
                  (i & 0xff000000) >> 24,
                  (i & 0x00ff0000) >> 16,
                  (i & 0x0000ff00) >> 8,
                  i & 0x000000ff]));
          i += 1;
      }
  
      return mask;
  }
  
  var SHA1_SIZE = 20;
  
  // Undo PKCS#1 (OAEP) padding and, if valid, return the plaintext
  function oaep_unpad(d, n, hash)
  {
      d = d.toByteArray();
  
      var i;
  
      for (i = 0; i < d.length; i += 1)
      {
          d[i] &= 0xff;
      }
  
      while (d.length < n)
      {
          d.unshift(0);
      }
  
      d = String.fromCharCode.apply(String, d);
  
      if (d.length < 2 * SHA1_SIZE + 2)
      {
          throw "Cipher too short";
      }
  
      var maskedSeed = d.substr(1, SHA1_SIZE)
      var maskedDB = d.substr(SHA1_SIZE + 1);
  
      var seedMask = oaep_mgf1_str(maskedDB, SHA1_SIZE, hash || rstr_sha1);
      var seed = [], i;
  
      for (i = 0; i < maskedSeed.length; i += 1)
      {
          seed[i] = maskedSeed.charCodeAt(i) ^ seedMask.charCodeAt(i);
      }
  
      var dbMask = oaep_mgf1_str(String.fromCharCode.apply(String, seed),
                             d.length - SHA1_SIZE, rstr_sha1);
  
      var DB = [];
  
      for (i = 0; i < maskedDB.length; i += 1)
      {
          DB[i] = maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i);
      }
  
      DB = String.fromCharCode.apply(String, DB);
  
      if (DB.substr(0, SHA1_SIZE) !== rstr_sha1(''))
      {
          throw "Hash mismatch";
      }
  
      DB = DB.substr(SHA1_SIZE);
  
      var first_one = DB.indexOf('\x01');
      var last_zero = (first_one != -1) ? DB.substr(0, first_one).lastIndexOf('\x00') : -1;
  
      if (last_zero + 1 != first_one)
      {
          throw "Malformed data";
      }
  
      return DB.substr(first_one + 1);
  }
  
  // Set the private key fields N, e, and d from hex strings
  function RSASetPrivate(N,E,D) {
    this.isPrivate = true;
    if (typeof N !== "string")
    {
      this.n = N;
      this.e = E;
      this.d = D;
    }
    else if(N != null && E != null && N.length > 0 && E.length > 0) {
      this.n = parseBigInt(N,16);
      this.e = parseInt(E,16);
      this.d = parseBigInt(D,16);
    }
    else
      alert("Invalid RSA private key");
  }
  
  // Set the private key fields N, e, d and CRT params from hex strings
  function RSASetPrivateEx(N,E,D,P,Q,DP,DQ,C) {
    this.isPrivate = true;
    if (N == null) throw "RSASetPrivateEx N == null";
    if (E == null) throw "RSASetPrivateEx E == null";
    if (N.length == 0) throw "RSASetPrivateEx N.length == 0";
    if (E.length == 0) throw "RSASetPrivateEx E.length == 0";
  
    if (N != null && E != null && N.length > 0 && E.length > 0) {
      this.n = parseBigInt(N,16);
      this.e = parseInt(E,16);
      this.d = parseBigInt(D,16);
      this.p = parseBigInt(P,16);
      this.q = parseBigInt(Q,16);
      this.dmp1 = parseBigInt(DP,16);
      this.dmq1 = parseBigInt(DQ,16);
      this.coeff = parseBigInt(C,16);
    } else {
      alert("Invalid RSA private key in RSASetPrivateEx");
    }
  }
  
  // Generate a new random private key B bits long, using public expt E
  function RSAGenerate(B,E) {
    var rng = new SecureRandom();
    var qs = B>>1;
    this.e = parseInt(E,16);
    var ee = new BigInteger(E,16);
    for(;;) {
      for(;;) {
        this.p = new BigInteger(B-qs,1,rng);
        if(this.p.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.p.isProbablePrime(10)) break;
      }
      for(;;) {
        this.q = new BigInteger(qs,1,rng);
        if(this.q.subtract(BigInteger.ONE).gcd(ee).compareTo(BigInteger.ONE) == 0 && this.q.isProbablePrime(10)) break;
      }
      if(this.p.compareTo(this.q) <= 0) {
        var t = this.p;
        this.p = this.q;
        this.q = t;
      }
      var p1 = this.p.subtract(BigInteger.ONE);	// p1 = p - 1
      var q1 = this.q.subtract(BigInteger.ONE);	// q1 = q - 1
      var phi = p1.multiply(q1);
      if(phi.gcd(ee).compareTo(BigInteger.ONE) == 0) {
        this.n = this.p.multiply(this.q);	// this.n = p * q
        this.d = ee.modInverse(phi);	// this.d = 
        this.dmp1 = this.d.mod(p1);	// this.dmp1 = d mod (p - 1)
        this.dmq1 = this.d.mod(q1);	// this.dmq1 = d mod (q - 1)
        this.coeff = this.q.modInverse(this.p);	// this.coeff = (q ^ -1) mod p
        break;
      }
    }
  }
  
  // Perform raw private operation on "x": return x^d (mod n)
  function RSADoPrivate(x) {
    if(this.p == null || this.q == null)
      return x.modPow(this.d, this.n);
  
    // TODO: re-calculate any missing CRT params
    var xp = x.mod(this.p).modPow(this.dmp1, this.p); // xp=cp?
    var xq = x.mod(this.q).modPow(this.dmq1, this.q); // xq=cq?
  
    while(xp.compareTo(xq) < 0)
      xp = xp.add(this.p);
    // NOTE:
    // xp.subtract(xq) => cp -cq
    // xp.subtract(xq).multiply(this.coeff).mod(this.p) => (cp - cq) * u mod p = h
    // xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq) => cq + (h * q) = M
    return xp.subtract(xq).multiply(this.coeff).mod(this.p).multiply(this.q).add(xq);
  }
  
  // Return the PKCS#1 RSA decryption of "ctext".
  // "ctext" is an even-length hex string and the output is a plain string.
  function RSADecrypt(ctext) {
    var c = parseBigInt(ctext, 16);
    var m = this.doPrivate(c);
    if(m == null) return null;
    return pkcs1unpad2(m, (this.n.bitLength()+7)>>3);
  }
  
  // Return the PKCS#1 OAEP RSA decryption of "ctext".
  // "ctext" is an even-length hex string and the output is a plain string.
  function RSADecryptOAEP(ctext, hash) {
    var c = parseBigInt(ctext, 16);
    var m = this.doPrivate(c);
    if(m == null) return null;
    return oaep_unpad(m, (this.n.bitLength()+7)>>3, hash);
  }
  
  // Return the PKCS#1 RSA decryption of "ctext".
  // "ctext" is a Base64-encoded string and the output is a plain string.
  //function RSAB64Decrypt(ctext) {
  //  var h = b64tohex(ctext);
  //  if(h) return this.decrypt(h); else return null;
  //}
  
  // protected
  RSAKey.prototype.doPrivate = RSADoPrivate;
  
  // public
  RSAKey.prototype.setPrivate = RSASetPrivate;
  RSAKey.prototype.setPrivateEx = RSASetPrivateEx;
  RSAKey.prototype.generate = RSAGenerate;
  RSAKey.prototype.decrypt = RSADecrypt;
  RSAKey.prototype.decryptOAEP = RSADecryptOAEP;
  //RSAKey.prototype.b64_decrypt = RSAB64Decrypt;
  