Skip to content

Commit 20e9e9f

Browse files
committed
we've not been compatible with MRI's DES (EDE) - partly due DES(3) ECB mode default
fixing jruby/jruby#2617 as well as jruby/jruby#931
1 parent e0a1399 commit 20e9e9f

File tree

3 files changed

+188
-39
lines changed

3 files changed

+188
-39
lines changed

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

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,11 @@ static Map<String, String[]> allSupportedCiphers() {
338338

339339
modes = cipherModes("DESede");
340340
if ( modes != null ) {
341-
supportedCiphers.put( "DES-EDE", new String[] { "DES", "CBC", "EDE", "DESede/CBC" } );
342-
supportedCiphers.put( "DES-EDE-CBC", supportedCiphers.get("DES-EDE") );
341+
supportedCiphers.put( "DES-EDE", new String[] { "DES", "ECB", "EDE", "DESede/ECB" } );
342+
supportedCiphers.put( "DES-EDE-CBC", new String[] { "DES", "CBC", "EDE", "DESede/CBC" } );
343343
supportedCiphers.put( "DES-EDE-CFB", new String[] { "DES", "CBC", "EDE", "DESede/CFB" } );
344344
supportedCiphers.put( "DES-EDE-OFB", new String[] { "DES", "CBC", "EDE", "DESede/OFB" } );
345-
supportedCiphers.put( "DES-EDE3", new String[] { "DES", "CBC", "EDE3", "DESede/CBC" });
345+
supportedCiphers.put( "DES-EDE3", new String[] { "DES", "ECB", "EDE3", "DESede/ECB" });
346346
for ( final String mode : modes ) {
347347
supportedCiphers.put( "DES-EDE3-" + mode, new String[] { "DES", mode, "EDE3", "DESede/" + mode });
348348
}
@@ -427,18 +427,21 @@ private static Algorithm osslToJava(final String osslName, final String padding)
427427
alg.realName = algVals[3];
428428
alg.realNameNeedsPadding = true;
429429
alg.padding = getPaddingType(padding, cryptoMode);
430+
431+
System.out.println(osslName + " alg = " + alg);
432+
430433
return alg;
431434
}
432435

433-
String cryptoBase, cryptoVersion = null, cryptoMode = "CBC", realName;
434-
String paddingType;
436+
String cryptoBase, cryptoVersion = null, cryptoMode, realName;
437+
String paddingType = null;
435438

436439
// EXPERIMENTAL: if there's '/' assume it's a "real" JCE name :
437440
if ( osslName.indexOf('/') != -1 ) {
438441
// e.g. "DESedeWrap/CBC/NOPADDING"
439442
final List names = StringHelper.split((CharSequence) osslName, '/');
440443
cryptoBase = (String) names.get(0);
441-
if ( names.size() > 1 ) cryptoMode = (String) names.get(1);
444+
cryptoMode = names.size() > 1 ? (String) names.get(1) : "CBC";
442445
paddingType = getPaddingType(padding, cryptoMode);
443446
if ( names.size() > 2 ) paddingType = (String) names.get(2);
444447
Algorithm alg = new Algorithm(cryptoBase, null, cryptoMode);
@@ -449,7 +452,7 @@ private static Algorithm osslToJava(final String osslName, final String padding)
449452

450453
int s = osslName.indexOf('-'); int i = 0;
451454
if (s == -1) {
452-
cryptoBase = osslName;
455+
cryptoBase = osslName; cryptoMode = null;
453456
}
454457
else {
455458
cryptoBase = osslName.substring(i, s);
@@ -471,19 +474,19 @@ private static Algorithm osslToJava(final String osslName, final String padding)
471474
cryptoBase = cryptoBase.toUpperCase(); // allways upper e.g. "AES"
472475
if ( cryptoMode != null ) cryptoMode = cryptoMode.toUpperCase();
473476

474-
boolean realNameSet = false;
477+
boolean realNameSet = false; boolean setDefaultCryptoMode = true;
475478

476479
if ( "BF".equals(cryptoBase) ) realName = "Blowfish";
477480
else if ( "CAST".equals(cryptoBase) ) realName = "CAST5";
478481
else if ( cryptoBase.startsWith("DES") ) {
479482
if ( "DES3".equals(cryptoBase) ) {
480-
cryptoBase = "DES"; realName = "DESede"; // cryptoVersion = cryptoMode; cryptoMode = "CBC";
483+
cryptoBase = "DES"; realName = "DESede"; cryptoVersion = "EDE3"; // cryptoMode = "CBC";
481484
}
482485
else if ( "EDE3".equalsIgnoreCase(cryptoVersion) || "EDE".equalsIgnoreCase(cryptoVersion) ) {
483-
realName = "DESede";
486+
realName = "DESede"; if ( cryptoMode == null ) cryptoMode = "ECB";
484487
}
485488
else if ( "EDE3".equalsIgnoreCase(cryptoMode) || "EDE".equalsIgnoreCase(cryptoMode) ) {
486-
realName = "DESede"; cryptoVersion = cryptoMode; cryptoMode = "CBC";
489+
realName = "DESede"; cryptoVersion = cryptoMode; cryptoMode = "ECB";
487490
}
488491
else realName = "DES";
489492
}
@@ -513,12 +516,14 @@ else if ( cryptoBase.length() > 8 && cryptoBase.startsWith("CAMELLIA") ) {
513516
cryptoVersion = cryptoMode;
514517
}
515518
cryptoMode = null; // padding = null;
519+
setDefaultCryptoMode = false;
516520
// cryptoMode = "NONE"; paddingType = "NoPadding";
517521
realNameSet = true;
518522
}
519523
}
520524

521-
paddingType = getPaddingType(padding, cryptoMode);
525+
if ( cryptoMode == null && setDefaultCryptoMode ) cryptoMode = "CBC";
526+
if ( paddingType == null ) paddingType = getPaddingType(padding, cryptoMode);
522527

523528
if ( cryptoMode != null ) {
524529
//if ( ! KNOWN_BLOCK_MODES.contains(cryptoMode) ) {
@@ -551,9 +556,8 @@ String getPadding() {
551556
}
552557

553558
private static String getPaddingType(final String padding, final String cryptoMode) {
554-
559+
//if ( "ECB".equals(cryptoMode) ) return "NoPadding";
555560
// TODO check cryptoMode CFB/OFB
556-
557561
final String defaultPadding = "PKCS5Padding";
558562

559563
if ( padding == null ) return defaultPadding;
@@ -615,8 +619,11 @@ public int getIvLength() {
615619
//else if ( "DES".equals(base) ) {
616620
// ivLength = 8;
617621
//}
618-
else if ( "RC4".equals(base) ) {
619-
ivLength = 8;
622+
//else if ( "RC4".equals(base) ) {
623+
// ivLength = 8;
624+
//}
625+
else if ( "ECB".equals(mode) ) {
626+
ivLength = 0;
620627
}
621628
else {
622629
ivLength = 8;
@@ -639,11 +646,9 @@ public int getKeyLength() {
639646
}
640647
if ( keyLen == -1 ) {
641648
if ( "DES".equals(base) ) {
642-
if ( "EDE3".equalsIgnoreCase(version) ) {
643-
keyLen = 24;
644-
} else {
645-
keyLen = 8;
646-
}
649+
if ( "EDE".equalsIgnoreCase(version) ) keyLen = 16;
650+
else if ( "EDE3".equalsIgnoreCase(version) ) keyLen = 24;
651+
else keyLen = 8;
647652
}
648653
else if ( "RC4".equals(base) ) {
649654
keyLen = 16;
@@ -715,22 +720,23 @@ public Cipher(Ruby runtime, RubyClass type) {
715720
private String padding;
716721

717722
private void dumpVars(final PrintStream out, final String header) {
718-
out.println(this.toString() + ' ' + header);
719-
out.println(" name = " + name);
720-
out.println(" cryptoBase = " + cryptoBase);
721-
out.println(" cryptoVersion = " + cryptoVersion);
722-
out.println(" cryptoMode = " + cryptoMode);
723-
out.println(" padding_type = " + paddingType);
724-
out.println(" realName = " + realName);
725-
out.println(" keyLength = " + keyLength);
726-
out.println(" ivLength = " + ivLength);
727-
out.println(" cipher.alg = " + cipher == null ? null : cipher.getAlgorithm());
728-
out.println(" cipher.blockSize = " + cipher == null ? null : cipher.getBlockSize());
729-
out.println(" encryptMode = " + encryptMode);
730-
out.println(" cipherInited = " + cipherInited);
731-
out.println(" key.length = " + (key == null ? 0 : key.length));
732-
out.println(" iv.length = " + (realIV == null ? 0 : realIV.length));
733-
out.println(" padding = " + padding);
723+
out.println(this.toString() + ' ' + header +
724+
"\n" +
725+
" name = " + name +
726+
" cryptoBase = " + cryptoBase +
727+
" cryptoVersion = " + cryptoVersion +
728+
" cryptoMode = " + cryptoMode +
729+
" padding_type = " + paddingType +
730+
" realName = " + realName +
731+
" keyLength = " + keyLength +
732+
" ivLength = " + ivLength +
733+
"\n" +
734+
" cipher.alg = " + (cipher == null ? null : cipher.getAlgorithm()) +
735+
" cipher.blockSize = " + (cipher == null ? null : cipher.getBlockSize()) +
736+
" encryptMode = " + encryptMode + " cipherInited = " + cipherInited +
737+
" key.length = " + (key == null ? 0 : key.length) +
738+
" iv.length = " + (realIV == null ? 0 : realIV.length) +
739+
" padding = " + padding);
734740
}
735741

736742
@JRubyMethod(required = 1, visibility = Visibility.PRIVATE)

src/test/java/org/jruby/ext/openssl/CipherTest.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,42 @@ private void doTestOsslToJsse() {
6363
assertEquals("CBC", alg.mode);
6464
assertEquals("DESede/CBC/PKCS5Padding", alg.getRealName());
6565

66+
alg = Cipher.Algorithm.osslToJava("DES3");
67+
assertEquals("DES", alg.base);
68+
assertEquals("EDE3", alg.version);
69+
assertEquals("CBC", alg.mode);
70+
assertEquals("DESede/CBC/PKCS5Padding", alg.getRealName());
71+
6672
alg = Cipher.Algorithm.osslToJava("DES-EDE");
6773
assertEquals("DES", alg.base);
6874
assertEquals("EDE", alg.version);
75+
assertEquals("ECB", alg.mode);
76+
assertEquals("DESede/ECB/PKCS5Padding", alg.getRealName());
77+
78+
alg = Cipher.Algorithm.osslToJava("DES-EDE3");
79+
assertEquals("DES", alg.base);
80+
assertEquals("EDE3", alg.version);
81+
assertEquals("ECB", alg.mode);
82+
assertEquals("DESede/ECB/PKCS5Padding", alg.getRealName());
83+
84+
alg = Cipher.Algorithm.osslToJava("DES-EDE-CBC");
85+
assertEquals("DES", alg.base);
86+
assertEquals("EDE", alg.version);
6987
assertEquals("CBC", alg.mode);
7088
assertEquals("DESede/CBC/PKCS5Padding", alg.getRealName());
7189

90+
alg = Cipher.Algorithm.osslToJava("DES-EDE3-CBC");
91+
assertEquals("DES", alg.base);
92+
assertEquals("EDE3", alg.version);
93+
assertEquals("CBC", alg.mode);
94+
assertEquals("DESede/CBC/PKCS5Padding", alg.getRealName());
95+
96+
alg = Cipher.Algorithm.osslToJava("DES-EDE3-CFB");
97+
assertEquals("DES", alg.base);
98+
assertEquals("EDE3", alg.version);
99+
assertEquals("CFB", alg.mode);
100+
assertEquals("DESede/CFB/PKCS5Padding", alg.getRealName());
101+
72102
alg = Cipher.Algorithm.osslToJava("DES-CFB");
73103
assertEquals("DES", alg.base);
74104
assertEquals(null, alg.version);
@@ -121,7 +151,7 @@ private void doTestOsslToJsse() {
121151
assertEquals("RC4", alg.getRealName());
122152

123153
// keeps "invalid" modes :
124-
154+
125155
alg = Cipher.Algorithm.osslToJava("DES-3X3");
126156
assertEquals("DES", alg.base);
127157
assertEquals(null, alg.version);

src/test/ruby/test_cipher.rb

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def test_excludes_cfb1_ciphers # due no support in BC for CFB-1
7575
assert ! OpenSSL::Cipher.ciphers.find { |name| name =~ /CFB1/i }
7676
end if defined? JRUBY_VERSION
7777

78-
def test_encrypt_decrypt_des_ede3 # borrowed from OpenSSL suite
78+
def test_encrypt_decrypt_des_ede3_cbc # borrowed from OpenSSL suite
7979
c1 = OpenSSL::Cipher::Cipher.new("DES-EDE3-CBC")
8080
c2 = OpenSSL::Cipher::DES.new(:EDE3, "CBC")
8181
key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
@@ -95,6 +95,119 @@ def test_encrypt_decrypt_des_ede3 # borrowed from OpenSSL suite
9595
assert_equal(data, c2.update(s2) + c2.final, "decrypt")
9696
end
9797

98+
def test_des_key_len
99+
cipher = OpenSSL::Cipher.new 'des'
100+
assert_equal 8, cipher.key_len
101+
cipher = OpenSSL::Cipher.new 'DES3'
102+
assert_equal 24, cipher.key_len
103+
104+
cipher = OpenSSL::Cipher.new 'DES-CBC'
105+
assert_equal 8, cipher.key_len
106+
cipher = OpenSSL::Cipher.new 'des-ede3'
107+
assert_equal 24, cipher.key_len
108+
109+
cipher = OpenSSL::Cipher.new 'des-ede'
110+
assert_equal 16, cipher.key_len
111+
cipher = OpenSSL::Cipher.new 'DES-EDE-CFB'
112+
assert_equal 16, cipher.key_len
113+
end
114+
115+
def test_des_iv_len
116+
cipher = OpenSSL::Cipher.new 'des'
117+
assert_equal 8, cipher.iv_len
118+
cipher = OpenSSL::Cipher.new 'DES3'
119+
assert_equal 8, cipher.iv_len
120+
121+
cipher = OpenSSL::Cipher.new 'DES-CBC'
122+
assert_equal 8, cipher.iv_len
123+
cipher = OpenSSL::Cipher.new 'des-ede3'
124+
assert_equal 0, cipher.iv_len
125+
126+
cipher = OpenSSL::Cipher.new 'des-ede'
127+
assert_equal 0, cipher.iv_len
128+
cipher = OpenSSL::Cipher.new 'DES-EDE-CFB'
129+
assert_equal 8, cipher.iv_len
130+
end
131+
132+
@@test_encrypt_decrypt_des_variations = nil
133+
134+
def test_encrypt_decrypt_des_variations
135+
key = "\0\0\0\0\0\0\0\0" * 3
136+
iv = "\0\0\0\0\0\0\0\0"
137+
data = "JPMNT"
138+
139+
{ # calculated on MRI
140+
'des' => "b\x00<\xC0\x16\xAF\xDCd",
141+
'des-cbc' => "b\x00<\xC0\x16\xAF\xDCd",
142+
#'des-cfb' => "\xE0\x9ER\xCC\xD8",
143+
#'des-ofb' => "\xE0\x9ER\xCC\xD8",
144+
'des-ecb' => ".\x1E\xB3\x0E\xE0\xD2\x9DG",
145+
146+
'des-ede' => "@\x8B\x89}u\xB4\r\xA5",
147+
'des-ede-cbc' => "\x99\x97\xBE(\xB9+f\xFA",
148+
#'des-ede-cfb' => "l\x02?\x16\x1A",
149+
#'des-ede-ofb' => "l\x02?\x16\x1A",
150+
##'des-ede-ecb' => RuntimeError: unsupported cipher algorithm (des-ede-ecb)
151+
152+
'des-ede3' => "\xDC\xD4\xF4\xBDmF\xC26", # actually ECB
153+
'des-ede3-cbc' => "\x8D\xE6\x17\xD0\x97\rR\x8C",
154+
#'des-ede3-cfb' => ",\x93^\xAD\x9C",
155+
#'des-ede3-ofb' => ",\x93^\xAD\x9C",
156+
##'des-ede3-ecb' => unsupported cipher algorithm (des-ede3-ecb)
157+
'des3' => "\x8D\xE6\x17\xD0\x97\rR\x8C"
158+
}.each do |name, expected|
159+
c = OpenSSL::Cipher.new name
160+
c.encrypt
161+
c.key = key
162+
c.iv = iv
163+
c.pkcs5_keyivgen(key, iv)
164+
165+
assert_equal expected, c.update(data) + c.final, "failed: #{name}"
166+
end
167+
168+
cipher = OpenSSL::Cipher::Cipher.new("DES-EDE3")
169+
170+
cipher.encrypt.pkcs5_keyivgen(key, iv)
171+
secret = cipher.update(data) + cipher.final
172+
assert_equal "\xDC\xD4\xF4\xBDmF\xC26", secret
173+
174+
cipher.decrypt.pkcs5_keyivgen(key, iv)
175+
assert_equal(data, cipher.update(secret) + cipher.final, "decrypt")
176+
177+
data = "sa jej lubim alebo moj bicykel"
178+
179+
cipher.encrypt.pkcs5_keyivgen(key, iv)
180+
secret = cipher.update(data) + cipher.final
181+
assert_equal "\xE9;\xDF\xEE/\x1D\xCB\xF9\xD1\xAF\xBC\xF0\x00\xA3\xDBsLxF2\xA4|\x11T\xD7&:\xD8\xF7\xA2\xD1b", secret
182+
183+
cipher.decrypt.pkcs5_keyivgen(key, iv)
184+
assert_equal(data, cipher.update(secret) + cipher.final, "decrypt")
185+
186+
cipher.padding = 0
187+
data = "hehehehemehehehe"
188+
189+
cipher.encrypt.pkcs5_keyivgen(key, iv)
190+
secret = cipher.update(data) + cipher.final
191+
assert_equal "v\r\xA4\xB3\x02\x18\xB5|A\x13\x87\xF1\xC0A\xC4U", secret
192+
193+
cipher.decrypt.pkcs5_keyivgen(key, iv)
194+
assert_equal(data, cipher.update(secret) + cipher.final, "decrypt")
195+
196+
# assuming Cipher.ciphers not cached - re-run the tests with cache :
197+
unless @@test_encrypt_decrypt_des_variations
198+
@@test_encrypt_decrypt_des_variations = true
199+
OpenSSL::Cipher.ciphers; test_encrypt_decrypt_des_variations
200+
end
201+
end
202+
203+
def test_another_encrypt_des_ede3
204+
cipher = OpenSSL::Cipher.new('DES-EDE3')
205+
cipher.encrypt # calculated on MRI :
206+
cipher.key = "\x1F\xFF&\xA4k\x8F^\xC80\txq'S\x93\xD2\xE3A\xEDT\xDCs\xFD<=G\a\x8F=\x8FhE"
207+
cipher.iv = "o\x15# \xD1\a\x90\xC7ZO\r[\xE2\x8F\v)# I6;\xE6\xB7h\xD3M\xDA\xA0\xD1\xDCy\xD2"
208+
assert_equal "\xE1\x8DZ>MEq\xEF\x1A\xAC\xB1ab\x0Ea\x81", (cipher.update('sup3rs33kr3t') + cipher.final)
209+
end
210+
98211
def test_random
99212
cipher = OpenSSL::Cipher.new 'AES-128-OFB'
100213

0 commit comments

Comments
 (0)