PageRenderTime 78ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/src/java/org/jruby/ext/openssl/Cipher.java

https://github.com/duncanmak/jruby-ossl
Java | 778 lines | 642 code | 83 blank | 53 comment | 142 complexity | 89935b86ac2101a29be2de87c8ee2d95 MD5 | raw file
  1. /***** BEGIN LICENSE BLOCK *****
  2. * Version: CPL 1.0/GPL 2.0/LGPL 2.1
  3. *
  4. * The contents of this file are subject to the Common Public
  5. * License Version 1.0 (the "License"); you may not use this file
  6. * except in compliance with the License. You may obtain a copy of
  7. * the License at http://www.eclipse.org/legal/cpl-v10.html
  8. *
  9. * Software distributed under the License is distributed on an "AS
  10. * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11. * implied. See the License for the specific language governing
  12. * rights and limitations under the License.
  13. *
  14. * Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com>
  15. *
  16. * Alternatively, the contents of this file may be used under the terms of
  17. * either of the GNU General Public License Version 2 or later (the "GPL"),
  18. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  19. * in which case the provisions of the GPL or the LGPL are applicable instead
  20. * of those above. If you wish to allow use of your version of this file only
  21. * under the terms of either the GPL or the LGPL, and not to allow others to
  22. * use your version of this file under the terms of the CPL, indicate your
  23. * decision by deleting the provisions above and replace them with the notice
  24. * and other provisions required by the GPL or the LGPL. If you do not delete
  25. * the provisions above, a recipient may use your version of this file under
  26. * the terms of any one of the CPL, the GPL or the LGPL.
  27. ***** END LICENSE BLOCK *****/
  28. package org.jruby.ext.openssl;
  29. import java.security.GeneralSecurityException;
  30. import java.security.MessageDigest;
  31. import java.security.NoSuchAlgorithmException;
  32. import java.util.ArrayList;
  33. import java.util.HashSet;
  34. import java.util.List;
  35. import java.util.Set;
  36. import javax.crypto.spec.IvParameterSpec;
  37. import javax.crypto.spec.RC2ParameterSpec;
  38. import org.jruby.Ruby;
  39. import org.jruby.RubyClass;
  40. import org.jruby.RubyModule;
  41. import org.jruby.RubyNumeric;
  42. import org.jruby.RubyObject;
  43. import org.jruby.common.IRubyWarnings;
  44. import org.jruby.common.IRubyWarnings.ID;
  45. import org.jruby.anno.JRubyMethod;
  46. import org.jruby.anno.JRubyModule;
  47. import org.jruby.exceptions.RaiseException;
  48. import org.jruby.runtime.ObjectAllocator;
  49. import org.jruby.runtime.builtin.IRubyObject;
  50. import org.jruby.util.ByteList;
  51. /**
  52. * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
  53. */
  54. public class Cipher extends RubyObject {
  55. private static final long serialVersionUID = 7727377435222646536L;
  56. // set to enable debug output
  57. private static final boolean DEBUG = false;
  58. private static ObjectAllocator CIPHER_ALLOCATOR = new ObjectAllocator() {
  59. public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  60. return new Cipher(runtime, klass);
  61. }
  62. };
  63. public static void createCipher(Ruby runtime, RubyModule mOSSL) {
  64. RubyClass cCipher = mOSSL.defineClassUnder("Cipher", runtime.getObject(), CIPHER_ALLOCATOR);
  65. cCipher.defineAnnotatedMethods(Cipher.class);
  66. cCipher.defineAnnotatedMethods(CipherModule.class);
  67. RubyClass openSSLError = mOSSL.getClass("OpenSSLError");
  68. cCipher.defineClassUnder("CipherError", openSSLError, openSSLError.getAllocator());
  69. }
  70. @JRubyModule(name = "OpenSSL::Cipher")
  71. public static class CipherModule {
  72. @JRubyMethod(meta = true)
  73. public static IRubyObject ciphers(IRubyObject recv) {
  74. initializeCiphers();
  75. List<IRubyObject> result = new ArrayList<IRubyObject>();
  76. for (String cipher : CIPHERS) {
  77. result.add(recv.getRuntime().newString(cipher));
  78. result.add(recv.getRuntime().newString(cipher.toLowerCase()));
  79. }
  80. return recv.getRuntime().newArray(result);
  81. }
  82. public static boolean isSupportedCipher(String name) {
  83. initializeCiphers();
  84. return CIPHERS.indexOf(name.toUpperCase()) != -1;
  85. }
  86. private static boolean initialized = false;
  87. private static final List<String> CIPHERS = new ArrayList<String>();
  88. private static void initializeCiphers() {
  89. synchronized (CIPHERS) {
  90. if (initialized) {
  91. return;
  92. }
  93. String[] other = {"AES128", "AES192", "AES256", "BLOWFISH", "RC2-40-CBC", "RC2-64-CBC", "RC4", "RC4-40", "CAST", "CAST-CBC"};
  94. String[] bases = {"AES-128", "AES-192", "AES-256", "BF", "DES", "DES-EDE", "DES-EDE3", "RC2", "CAST5"};
  95. String[] suffixes = {"", "-CBC", "-CFB", "-CFB1", "-CFB8", "-ECB", "-OFB"};
  96. for (int i = 0, j = bases.length; i < j; i++) {
  97. for (int k = 0, l = suffixes.length; k < l; k++) {
  98. String val = bases[i] + suffixes[k];
  99. if (tryCipher(val)) {
  100. CIPHERS.add(val.toUpperCase());
  101. }
  102. }
  103. }
  104. for (int i = 0, j = other.length; i < j; i++) {
  105. if (tryCipher(other[i])) {
  106. CIPHERS.add(other[i].toUpperCase());
  107. }
  108. }
  109. initialized = true;
  110. }
  111. }
  112. }
  113. public static class Algorithm {
  114. private static final Set<String> BLOCK_MODES;
  115. static {
  116. BLOCK_MODES = new HashSet<String>();
  117. BLOCK_MODES.add("CBC");
  118. BLOCK_MODES.add("CFB");
  119. BLOCK_MODES.add("CFB1");
  120. BLOCK_MODES.add("CFB8");
  121. BLOCK_MODES.add("ECB");
  122. BLOCK_MODES.add("OFB");
  123. }
  124. public static String jsseToOssl(String inName, int keyLen) {
  125. String cryptoBase = null;
  126. String cryptoVersion = null;
  127. String cryptoMode = null;
  128. String[] parts = inName.split("/");
  129. if (parts.length != 1 && parts.length != 3) {
  130. return null;
  131. }
  132. cryptoBase = parts[0];
  133. if (parts.length > 2) {
  134. cryptoMode = parts[1];
  135. // padding: parts[2] is not used
  136. }
  137. if (!BLOCK_MODES.contains(cryptoMode)) {
  138. cryptoVersion = cryptoMode;
  139. cryptoMode = "CBC";
  140. }
  141. if (cryptoMode == null) {
  142. cryptoMode = "CBC";
  143. }
  144. if (cryptoBase.equals("DESede")) {
  145. cryptoBase = "DES";
  146. cryptoVersion = "EDE3";
  147. } else if (cryptoBase.equals("Blowfish")) {
  148. cryptoBase = "BF";
  149. }
  150. if (cryptoVersion == null) {
  151. cryptoVersion = String.valueOf(keyLen);
  152. }
  153. return cryptoBase + "-" + cryptoVersion + "-" + cryptoMode;
  154. }
  155. public static String[] osslToJsse(String inName) {
  156. // assume PKCS5Padding
  157. return osslToJsse(inName, null);
  158. }
  159. public static String[] osslToJsse(String inName, String padding) {
  160. String[] split = inName.split("-");
  161. String cryptoBase = split[0];
  162. String cryptoVersion = null;
  163. String cryptoMode = null;
  164. String realName = null;
  165. String paddingType;
  166. if (padding == null || padding.equalsIgnoreCase("PKCS5Padding")) {
  167. paddingType = "PKCS5Padding";
  168. } else if (padding.equals("0") || padding.equalsIgnoreCase("NoPadding")) {
  169. paddingType = "NoPadding";
  170. } else if (padding.equalsIgnoreCase("ISO10126Padding")) {
  171. paddingType = "ISO10126Padding";
  172. } else {
  173. paddingType = "PKCS5Padding";
  174. }
  175. if ("bf".equalsIgnoreCase(cryptoBase)) {
  176. cryptoBase = "Blowfish";
  177. }
  178. if (split.length == 3) {
  179. cryptoVersion = split[1];
  180. cryptoMode = split[2];
  181. } else if (split.length == 2) {
  182. cryptoMode = split[1];
  183. } else {
  184. cryptoMode = "CBC";
  185. }
  186. if (cryptoBase.equalsIgnoreCase("CAST")) {
  187. realName = "CAST5";
  188. } else if (cryptoBase.equalsIgnoreCase("DES") && "EDE3".equalsIgnoreCase(cryptoVersion)) {
  189. realName = "DESede";
  190. } else {
  191. realName = cryptoBase;
  192. }
  193. if (!BLOCK_MODES.contains(cryptoMode.toUpperCase())) {
  194. cryptoVersion = cryptoMode;
  195. cryptoMode = "CBC";
  196. } else if (cryptoMode.equalsIgnoreCase("CFB1")) {
  197. // uglish SunJCE cryptoMode normalization.
  198. cryptoMode = "CFB";
  199. }
  200. if (realName.equalsIgnoreCase("RC4")) {
  201. realName = "RC4";
  202. cryptoMode = "NONE";
  203. paddingType = "NoPadding";
  204. } else {
  205. realName = realName + "/" + cryptoMode + "/" + paddingType;
  206. }
  207. return new String[]{cryptoBase, cryptoVersion, cryptoMode, realName, paddingType};
  208. }
  209. public static int[] osslKeyIvLength(String name) {
  210. String[] values = Algorithm.osslToJsse(name);
  211. String cryptoBase = values[0];
  212. String cryptoVersion = values[1];
  213. String cryptoMode = values[2];
  214. String realName = values[3];
  215. int keyLen = -1;
  216. int ivLen = -1;
  217. if (hasLen(cryptoBase) && null != cryptoVersion) {
  218. try {
  219. keyLen = Integer.parseInt(cryptoVersion) / 8;
  220. } catch (NumberFormatException e) {
  221. keyLen = -1;
  222. }
  223. }
  224. if (keyLen == -1) {
  225. if ("DES".equalsIgnoreCase(cryptoBase)) {
  226. ivLen = 8;
  227. if ("EDE3".equalsIgnoreCase(cryptoVersion)) {
  228. keyLen = 24;
  229. } else {
  230. keyLen = 8;
  231. }
  232. } else if ("RC4".equalsIgnoreCase(cryptoBase)) {
  233. ivLen = 0;
  234. keyLen = 16;
  235. } else {
  236. keyLen = 16;
  237. try {
  238. if ((javax.crypto.Cipher.getMaxAllowedKeyLength(name) / 8) < keyLen) {
  239. keyLen = javax.crypto.Cipher.getMaxAllowedKeyLength(name) / 8;
  240. }
  241. } catch (Exception e) {
  242. // I hate checked exceptions
  243. }
  244. }
  245. }
  246. if (ivLen == -1) {
  247. if ("AES".equalsIgnoreCase(cryptoBase)) {
  248. ivLen = 16;
  249. } else {
  250. ivLen = 8;
  251. }
  252. }
  253. return new int[] { keyLen, ivLen };
  254. }
  255. public static boolean hasLen(String cryptoBase) {
  256. return "AES".equalsIgnoreCase(cryptoBase) || "RC2".equalsIgnoreCase(cryptoBase) || "RC4".equalsIgnoreCase(cryptoBase);
  257. }
  258. }
  259. private static boolean tryCipher(final String rubyName) {
  260. String cryptoMode = Algorithm.osslToJsse(rubyName, null)[3];
  261. try {
  262. javax.crypto.Cipher.getInstance(cryptoMode);
  263. return true;
  264. } catch (NoSuchAlgorithmException nsae) {
  265. try {
  266. OpenSSLReal.getCipherBC(cryptoMode);
  267. return true;
  268. } catch (GeneralSecurityException gse) {
  269. return false;
  270. }
  271. } catch (Exception e) {
  272. return false;
  273. }
  274. }
  275. public Cipher(Ruby runtime, RubyClass type) {
  276. super(runtime, type);
  277. }
  278. private javax.crypto.Cipher ciph;
  279. private String name;
  280. private String cryptoBase;
  281. private String cryptoVersion;
  282. private String cryptoMode;
  283. private String padding_type;
  284. private String realName;
  285. private int keyLen = -1;
  286. private int generateKeyLen = -1;
  287. private int ivLen = -1;
  288. private boolean encryptMode = true;
  289. //private IRubyObject[] modeParams;
  290. private boolean ciphInited = false;
  291. private byte[] key;
  292. private byte[] realIV;
  293. private byte[] orgIV;
  294. private String padding;
  295. void dumpVars() {
  296. System.out.println("***** Cipher instance vars ****");
  297. System.out.println("name = " + name);
  298. System.out.println("cryptoBase = " + cryptoBase);
  299. System.out.println("cryptoVersion = " + cryptoVersion);
  300. System.out.println("cryptoMode = " + cryptoMode);
  301. System.out.println("padding_type = " + padding_type);
  302. System.out.println("realName = " + realName);
  303. System.out.println("keyLen = " + keyLen);
  304. System.out.println("ivLen = " + ivLen);
  305. System.out.println("ciph block size = " + ciph.getBlockSize());
  306. System.out.println("encryptMode = " + encryptMode);
  307. System.out.println("ciphInited = " + ciphInited);
  308. System.out.println("key.length = " + (key == null ? 0 : key.length));
  309. System.out.println("iv.length = " + (this.realIV == null ? 0 : this.realIV.length));
  310. System.out.println("padding = " + padding);
  311. System.out.println("ciphAlgo = " + ciph.getAlgorithm());
  312. System.out.println("*******************************");
  313. }
  314. @JRubyMethod(required = 1)
  315. public IRubyObject initialize(IRubyObject str) {
  316. name = str.toString();
  317. if (!CipherModule.isSupportedCipher(name)) {
  318. throw newCipherError(getRuntime(), String.format("unsupported cipher algorithm (%s)", name));
  319. }
  320. String[] values = Algorithm.osslToJsse(name, padding);
  321. cryptoBase = values[0];
  322. cryptoVersion = values[1];
  323. cryptoMode = values[2];
  324. realName = values[3];
  325. padding_type = values[4];
  326. ciph = getCipher();
  327. int[] lengths = Algorithm.osslKeyIvLength(name);
  328. keyLen = lengths[0];
  329. ivLen = lengths[1];
  330. if ("DES".equalsIgnoreCase(cryptoBase)) {
  331. generateKeyLen = keyLen / 8 * 7;
  332. }
  333. // given 'rc4' must be 'RC4' here. OpenSSL checks it as a LN of object
  334. // ID and set SN. We don't check 'name' is allowed as a LN in ASN.1 for
  335. // the possibility of JCE specific algorithm so just do upperCase here
  336. // for OpenSSL compatibility.
  337. name = name.toUpperCase();
  338. return this;
  339. }
  340. @Override
  341. @JRubyMethod(required = 1)
  342. public IRubyObject initialize_copy(IRubyObject obj) {
  343. if (this == obj) {
  344. return this;
  345. }
  346. checkFrozen();
  347. cryptoBase = ((Cipher) obj).cryptoBase;
  348. cryptoVersion = ((Cipher) obj).cryptoVersion;
  349. cryptoMode = ((Cipher) obj).cryptoMode;
  350. padding_type = ((Cipher) obj).padding_type;
  351. realName = ((Cipher) obj).realName;
  352. name = ((Cipher) obj).name;
  353. keyLen = ((Cipher) obj).keyLen;
  354. ivLen = ((Cipher) obj).ivLen;
  355. encryptMode = ((Cipher) obj).encryptMode;
  356. ciphInited = false;
  357. if (((Cipher) obj).key != null) {
  358. key = new byte[((Cipher) obj).key.length];
  359. System.arraycopy(((Cipher) obj).key, 0, key, 0, key.length);
  360. } else {
  361. key = null;
  362. }
  363. if (((Cipher) obj).realIV != null) {
  364. this.realIV = new byte[((Cipher) obj).realIV.length];
  365. System.arraycopy(((Cipher) obj).realIV, 0, this.realIV, 0, this.realIV.length);
  366. } else {
  367. this.realIV = null;
  368. }
  369. this.orgIV = this.realIV;
  370. padding = ((Cipher) obj).padding;
  371. ciph = getCipher();
  372. return this;
  373. }
  374. @JRubyMethod
  375. public IRubyObject name() {
  376. return getRuntime().newString(name);
  377. }
  378. @JRubyMethod
  379. public IRubyObject key_len() {
  380. return getRuntime().newFixnum(keyLen);
  381. }
  382. @JRubyMethod
  383. public IRubyObject iv_len() {
  384. return getRuntime().newFixnum(ivLen);
  385. }
  386. @JRubyMethod(name = "key_len=", required = 1)
  387. public IRubyObject set_key_len(IRubyObject len) {
  388. this.keyLen = RubyNumeric.fix2int(len);
  389. return len;
  390. }
  391. @JRubyMethod(name = "key=", required = 1)
  392. public IRubyObject set_key(IRubyObject key) {
  393. byte[] keyBytes;
  394. try {
  395. keyBytes = key.convertToString().getBytes();
  396. } catch (Exception e) {
  397. if (DEBUG) {
  398. e.printStackTrace();
  399. }
  400. throw newCipherError(getRuntime(), e.getMessage());
  401. }
  402. if (keyBytes.length < keyLen) {
  403. throw newCipherError(getRuntime(), "key length to short");
  404. }
  405. if (keyBytes.length > keyLen) {
  406. byte[] keys = new byte[keyLen];
  407. System.arraycopy(keyBytes, 0, keys, 0, keyLen);
  408. keyBytes = keys;
  409. }
  410. this.key = keyBytes;
  411. return key;
  412. }
  413. @JRubyMethod(name = "iv=", required = 1)
  414. public IRubyObject set_iv(IRubyObject iv) {
  415. byte[] ivBytes;
  416. try {
  417. ivBytes = iv.convertToString().getBytes();
  418. } catch (Exception e) {
  419. if (DEBUG) {
  420. e.printStackTrace();
  421. }
  422. throw newCipherError(getRuntime(), e.getMessage());
  423. }
  424. if (ivBytes.length < ivLen) {
  425. throw newCipherError(getRuntime(), "iv length to short");
  426. } else {
  427. // EVP_CipherInit_ex uses leading IV length of given sequence.
  428. byte[] iv2 = new byte[ivLen];
  429. System.arraycopy(ivBytes, 0, iv2, 0, ivLen);
  430. this.realIV = iv2;
  431. }
  432. this.orgIV = this.realIV;
  433. if (!isStreamCipher()) {
  434. ciphInited = false;
  435. }
  436. return iv;
  437. }
  438. @JRubyMethod
  439. public IRubyObject block_size() {
  440. if (isStreamCipher()) {
  441. // getBlockSize() returns 0 for stream cipher in JCE. OpenSSL returns 1 for RC4.
  442. return getRuntime().newFixnum(1);
  443. }
  444. return getRuntime().newFixnum(ciph.getBlockSize());
  445. }
  446. protected void init(IRubyObject[] args, boolean encrypt) {
  447. org.jruby.runtime.Arity.checkArgumentCount(getRuntime(), args, 0, 2);
  448. encryptMode = encrypt;
  449. ciphInited = false;
  450. if (args.length > 0) {
  451. /*
  452. * oops. this code mistakes salt for IV.
  453. * We deprecated the arguments for this method, but we decided
  454. * keeping this behaviour for backward compatibility.
  455. */
  456. byte[] pass = args[0].convertToString().getBytes();
  457. byte[] iv = null;
  458. try {
  459. iv = "OpenSSL for Ruby rulez!".getBytes("ISO8859-1");
  460. byte[] iv2 = new byte[this.ivLen];
  461. System.arraycopy(iv, 0, iv2, 0, this.ivLen);
  462. iv = iv2;
  463. } catch (Exception e) {
  464. }
  465. if (args.length > 1 && !args[1].isNil()) {
  466. getRuntime().getWarnings().warning(ID.MISCELLANEOUS, "key derivation by " + getMetaClass().getRealClass().getName() + "#encrypt is deprecated; use " + getMetaClass().getRealClass().getName() + "::pkcs5_keyivgen instead");
  467. iv = args[1].convertToString().getBytes();
  468. if (iv.length > this.ivLen) {
  469. byte[] iv2 = new byte[this.ivLen];
  470. System.arraycopy(iv, 0, iv2, 0, this.ivLen);
  471. iv = iv2;
  472. }
  473. }
  474. MessageDigest digest = Digest.getDigest("MD5", getRuntime());
  475. OpenSSLImpl.KeyAndIv result = OpenSSLImpl.EVP_BytesToKey(keyLen, ivLen, digest, iv, pass, 2048);
  476. this.key = result.getKey();
  477. this.realIV = iv;
  478. this.orgIV = this.realIV;
  479. }
  480. }
  481. @JRubyMethod(optional = 2)
  482. public IRubyObject encrypt(IRubyObject[] args) {
  483. this.realIV = orgIV;
  484. init(args, true);
  485. return this;
  486. }
  487. @JRubyMethod(optional = 2)
  488. public IRubyObject decrypt(IRubyObject[] args) {
  489. this.realIV = orgIV;
  490. init(args, false);
  491. return this;
  492. }
  493. @JRubyMethod
  494. public IRubyObject reset() {
  495. if (!isStreamCipher()) {
  496. this.realIV = orgIV;
  497. doInitialize();
  498. }
  499. return this;
  500. }
  501. javax.crypto.Cipher getCipher() {
  502. try {
  503. return javax.crypto.Cipher.getInstance(realName);
  504. } catch (NoSuchAlgorithmException e) {
  505. try {
  506. return OpenSSLReal.getCipherBC(realName);
  507. } catch (GeneralSecurityException ignore) {
  508. }
  509. throw newCipherError(getRuntime(), "unsupported cipher algorithm (" + realName + ")");
  510. } catch (javax.crypto.NoSuchPaddingException e) {
  511. throw newCipherError(getRuntime(), "unsupported cipher padding (" + realName + ")");
  512. }
  513. }
  514. @JRubyMethod(required = 1, optional = 3)
  515. public IRubyObject pkcs5_keyivgen(IRubyObject[] args) {
  516. org.jruby.runtime.Arity.checkArgumentCount(getRuntime(), args, 1, 4);
  517. byte[] pass = args[0].convertToString().getBytes();
  518. byte[] salt = null;
  519. int iter = 2048;
  520. IRubyObject vdigest = getRuntime().getNil();
  521. if (args.length > 1) {
  522. if (!args[1].isNil()) {
  523. salt = args[1].convertToString().getBytes();
  524. }
  525. if (args.length > 2) {
  526. if (!args[2].isNil()) {
  527. iter = RubyNumeric.fix2int(args[2]);
  528. }
  529. if (args.length > 3) {
  530. vdigest = args[3];
  531. }
  532. }
  533. }
  534. if (null != salt) {
  535. if (salt.length != 8) {
  536. throw newCipherError(getRuntime(), "salt must be an 8-octet string");
  537. }
  538. }
  539. final String algorithm = vdigest.isNil() ? "MD5" : ((Digest) vdigest).getAlgorithm();
  540. MessageDigest digest = Digest.getDigest(algorithm, getRuntime());
  541. OpenSSLImpl.KeyAndIv result = OpenSSLImpl.EVP_BytesToKey(keyLen, ivLen, digest, salt, pass, iter);
  542. this.key = result.getKey();
  543. this.realIV = result.getIv();
  544. this.orgIV = this.realIV;
  545. doInitialize();
  546. return getRuntime().getNil();
  547. }
  548. private void doInitialize() {
  549. if (DEBUG) {
  550. System.out.println("*** doInitialize");
  551. dumpVars();
  552. }
  553. ciphInited = true;
  554. try {
  555. assert (key.length * 8 == keyLen) || (key.length == keyLen) : "Key wrong length";
  556. assert (this.realIV.length * 8 == ivLen) || (this.realIV.length == ivLen) : "IV wrong length";
  557. if (!"ECB".equalsIgnoreCase(cryptoMode)) {
  558. if (this.realIV == null) {
  559. this.realIV = new byte[ivLen];
  560. System.arraycopy("OpenSSL for JRuby rulez".getBytes(), 0,
  561. this.realIV, 0, ivLen);
  562. }
  563. if ("RC2".equalsIgnoreCase(cryptoBase)) {
  564. this.ciph.init(encryptMode ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE, new SimpleSecretKey("RC2", this.key), new RC2ParameterSpec(this.key.length * 8, this.realIV));
  565. } else if ("RC4".equalsIgnoreCase(cryptoBase)) {
  566. this.ciph.init(encryptMode ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE, new SimpleSecretKey("RC4", this.key));
  567. } else {
  568. this.ciph.init(encryptMode ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE, new SimpleSecretKey(realName.split("/")[0], this.key), new IvParameterSpec(this.realIV));
  569. }
  570. } else {
  571. this.ciph.init(encryptMode ? javax.crypto.Cipher.ENCRYPT_MODE : javax.crypto.Cipher.DECRYPT_MODE, new SimpleSecretKey(realName.split("/")[0], this.key));
  572. }
  573. } catch (Exception e) {
  574. if (DEBUG) {
  575. e.printStackTrace();
  576. }
  577. throw newCipherError(getRuntime(), e.getMessage());
  578. }
  579. }
  580. private byte[] lastIv = null;
  581. @JRubyMethod
  582. public IRubyObject update(IRubyObject data) {
  583. if (DEBUG) {
  584. System.out.println("*** update [" + data + "]");
  585. }
  586. byte[] val = data.convertToString().getBytes();
  587. if (val.length == 0) {
  588. throw getRuntime().newArgumentError("data must not be empty");
  589. }
  590. if (!ciphInited) {
  591. if (DEBUG) {
  592. System.out.println("BEFORE INITING");
  593. }
  594. doInitialize();
  595. if (DEBUG) {
  596. System.out.println("AFTER INITING");
  597. }
  598. }
  599. byte[] str = new byte[0];
  600. try {
  601. byte[] out = ciph.update(val);
  602. if (out != null) {
  603. str = out;
  604. if (this.realIV != null) {
  605. if (lastIv == null) {
  606. lastIv = new byte[ivLen];
  607. }
  608. byte[] tmpIv = encryptMode ? out : val;
  609. if (tmpIv.length >= ivLen) {
  610. System.arraycopy(tmpIv, tmpIv.length - ivLen, lastIv, 0, ivLen);
  611. }
  612. }
  613. }
  614. } catch (Exception e) {
  615. if (DEBUG) {
  616. e.printStackTrace();
  617. }
  618. throw newCipherError(getRuntime(), e.getMessage());
  619. }
  620. return getRuntime().newString(new ByteList(str, false));
  621. }
  622. @JRubyMethod(name = "<<")
  623. public IRubyObject update_deprecated(IRubyObject data) {
  624. getRuntime().getWarnings().warn(IRubyWarnings.ID.DEPRECATED_METHOD, "" + this.getMetaClass().getRealClass().getName() + "#<< is deprecated; use " + this.getMetaClass().getRealClass().getName() + "#update instead");
  625. return update(data);
  626. }
  627. @JRubyMethod(name = "final")
  628. public IRubyObject _final() {
  629. if (!ciphInited) {
  630. doInitialize();
  631. }
  632. // trying to allow update after final like cruby-openssl. Bad idea.
  633. if ("RC4".equalsIgnoreCase(cryptoBase)) {
  634. return getRuntime().newString("");
  635. }
  636. ByteList str = new ByteList(ByteList.NULL_ARRAY);
  637. try {
  638. byte[] out = ciph.doFinal();
  639. if (out != null) {
  640. str = new ByteList(out, false);
  641. // TODO: Modifying this line appears to fix the issue, but I do
  642. // not have a good reason for why. Best I can tell, lastIv needs
  643. // to be set regardless of encryptMode, so we'll go with this
  644. // for now. JRUBY-3335.
  645. //if(this.realIV != null && encryptMode) {
  646. if (this.realIV != null) {
  647. if (lastIv == null) {
  648. lastIv = new byte[ivLen];
  649. }
  650. byte[] tmpIv = out;
  651. if (tmpIv.length >= ivLen) {
  652. System.arraycopy(tmpIv, tmpIv.length - ivLen, lastIv, 0, ivLen);
  653. }
  654. }
  655. }
  656. if (this.realIV != null) {
  657. this.realIV = lastIv;
  658. doInitialize();
  659. }
  660. } catch (Exception e) {
  661. throw newCipherError(getRuntime(), e.getMessage());
  662. }
  663. return getRuntime().newString(str);
  664. }
  665. @JRubyMethod(name = "padding=")
  666. public IRubyObject set_padding(IRubyObject padding) {
  667. this.padding = padding.toString();
  668. initialize(getRuntime().newString(name));
  669. return padding;
  670. }
  671. String getAlgorithm() {
  672. return this.ciph.getAlgorithm();
  673. }
  674. String getName() {
  675. return this.name;
  676. }
  677. String getCryptoBase() {
  678. return this.cryptoBase;
  679. }
  680. String getCryptoMode() {
  681. return this.cryptoMode;
  682. }
  683. int getKeyLen() {
  684. return keyLen;
  685. }
  686. int getGenerateKeyLen() {
  687. return (generateKeyLen == -1) ? keyLen : generateKeyLen;
  688. }
  689. private boolean isStreamCipher() {
  690. return ciph.getBlockSize() == 0;
  691. }
  692. private static RaiseException newCipherError(Ruby runtime, String message) {
  693. return Utils.newError(runtime, "OpenSSL::Cipher::CipherError", message);
  694. }
  695. }