SMTP with CRAM-MD5 in Java
Asked Answered
C

7

6

I need to send email through an (external) SMTP server from Java however this server will only accept CRAM-MD5 authentication, which is not supported by JavaMail.

What would be a good way to get these emails to send? (It must be in Java.)

Chaparajos answered 9/10, 2008 at 11:28 Comment(0)
S
6

Here is thread which says that you need to add the following property:

props.put("mail.smtp.auth.mechanisms", "CRAM-MD5")

Also in Geronimo implementation there is CramMD5Authenticator

Hope it helps to resolve this old question.

Sinter answered 24/8, 2012 at 14:4 Comment(1)
i accepted this answer because it seems like the solution. Havent tested it because our need to use this disappearedChaparajos
A
5

Since Java Mail 1.4.4, CRAM-MD5 is supported for use with smtp. Just set this parameter to your properties and it will work:

props.put("mail.smtp.sasl.enable", "true");

Aeroscope answered 28/8, 2012 at 9:3 Comment(2)
One day to late to get the bounty.Myrtia
Setting mail.smtp.auth.mechanisms didn’t work for me, but this did (or possibly both are required).Letishaletitia
L
4

This doesn't help you directly, however, IMAP connections in JavaMail do support SASL (and thus CRAM-MD5, see the Java SASL documentation) if you set the mail.imap.sasl.enable boolean property to true.

Unfortunately, there is no mail.smtp.sasl.enable property, and SASL cannot be enabled for SMTP in JavaMail. :-(

However, you can download the JavaMail source code, and you can try to edit the SMTP code to support SASL in a similar manner to the IMAP code. Good luck!

Lymph answered 9/10, 2008 at 11:52 Comment(1)
The release notes of Java Mail 1.4.4 (oracle.com/technetwork/java/javamail/javamail144-1562675.html) indicate that there is now SASL Support for SMPT too.Myrtia
A
4

This probably won't help you but CRAM-MD5 and CRAM-SHA1 are fairly easy to implement assuming you have the correct library (md5/sha1) and, idealy, a base64 encoding library (though the base64 stuff is fairly easy to implement yourself in a pinch).

The transaction looks like this:

C: AUTH CRAM-MD5
S: 334 BASE64(NONCE)
C: BASE64(USERNAME, " ", MD5((SECRET XOR opad),MD5((SECRET XOR ipad), NONCE)))
S: 235 Authentication succeeded

Where NONCE is the once time challenge string, USERNAME is the username you are tryng to authenticate, SECRET is the shared secret ("password"), opad is 0x5C, and ipad is 0x36.

(CRAM-SHA1 would be the same transaction, but using SHA1() instead of MD5() to do the digesting)

So, here's an example of a real CRAM-MD5 transaction

C: AUTH CRAM-MD5
S: 334 PDQ1MDMuMTIyMzU1Nzg2MkBtYWlsMDEuZXhhbXBsZS5jb20+
C: dXNlckBleGFtcGxlLmNvbSA4YjdjODA5YzQ0NTNjZTVhYTA5N2VhNWM4OTlmNGY4Nw==
S: 235 Authentication succeeded

Backing up the process a step you get:

S: 334 BASE64("<[email protected]>")
C: BASE64("[email protected] 8b7c809c4453ce5aa097ea5c899f4f87")

Backing up one step further to before the digest is calculated, you get:

S: 334 BASE64("<[email protected]>")
C: BASE64("[email protected] ", MD5(("password" XOR opad),MD5(("password" XOR ipad), "<[email protected]>")))

I guess that's kind of confusing now that I write it out, but trust me, compared to trying to do NTLM/SPA by hand, it's a breeze. If you're motivated, it's actually pretty easy to implement. Or maybe I've just spent way to long with my hands in the guts of mail clients and servers to think about it clearly anymore...

Anastatius answered 9/10, 2008 at 13:32 Comment(3)
You don't want to do that. Java has SASL support built in, and it's much, much better to use Java's built-in implementation than to hand-code one. The trick is to make JavaMail support SASL for SMTP, not CRAM-MD5 as such.Lymph
"This probably won't help you..." This question just made me remember all my own work struggling with AUTH protocols (which I implemented from hand because I was building a tool to verify third party tools). I agree that it's generally a bad idea to reimplement if alternative is available.Anastatius
nods Fair enough, yes, I can imagine how much of a pain it would be to implement these by hand. Mwahahaha about SPA/NTLM, that's one I've had to battle with too (thanks to people using clients that only do SPA and not CRAM-*).Lymph
L
4

Very Simple CRAMMD5 program in JAVA


import java.security.*;

class CRAMMD5Test
{
public static void main(String[] args) throws Exception
{
    // This represents the BASE64 encoded timestamp sent by the POP server
    String dataString = Base64Decoder.decode("PDAwMDAuMDAwMDAwMDAwMEBteDEuc2VydmVyLmNvbT4=");
    byte[] data = dataString.getBytes();

    // The password to access the account
    byte[] key  = new String("password").getBytes();

    // The address of the e-mail account
    String user = "[email protected]";

    MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.reset();

    if (key.length > 64)
        key = md5.digest(key);

    byte[] k_ipad = new byte[64];
    byte[] k_opad = new byte[64];

    System.arraycopy(key, 0, k_ipad, 0, key.length);
    System.arraycopy(key, 0, k_opad, 0, key.length);

    for (int i=0; i<64; i++)
    {
        k_ipad[i] ^= 0x36;
        k_opad[i] ^= 0x5c;
    }

    byte[] i_temp = new byte[k_ipad.length + data.length];

    System.arraycopy(k_ipad, 0, i_temp, 0, k_ipad.length);
    System.arraycopy(data, 0, i_temp, k_ipad.length, data.length);

    i_temp = md5.digest(i_temp);

    byte[] o_temp = new byte[k_opad.length + i_temp.length];

    System.arraycopy(k_opad, 0, o_temp, 0, k_opad.length);
    System.arraycopy(i_temp, 0, o_temp, k_opad.length, i_temp.length);

        byte[] result = md5.digest(o_temp);
        StringBuffer hexString = new StringBuffer();

        for (int i=0;i < result.length; i++) {
                hexString.append(Integer.toHexString((result[i] >>> 4) & 0x0F));
                hexString.append(Integer.toHexString(0x0F & result[i]));
             }


        System.out.println(Base64Encoder.encode(user + " " + hexString.toString()));
    }
}
Laureenlaurel answered 31/12, 2008 at 17:46 Comment(0)
T
2

I tried the code on the example of a real CRAM-MD5 transaction, and also on the example given into the RFC 2195.

It does not work because the conversion to an hexadecimal string is not correct. For exmaple, with this code, you will obtain "b913a62c7eda7a495b4e6e7334d3890" instead of "b913a602c7eda7a495b4e6e7334d3890" and the sent authentication string will not be correct.

If you download the source code of javaMail, you will see the implementation of the function toHex into the unit "DigestMD5". Using this conversion, it will work.

Tristantristas answered 22/1, 2009 at 9:58 Comment(0)
L
1

Change:

for (int i=0; i<result.length; i++)
  hexString.append(Integer.toHexString(0xFF & result[i]));

To:

for (int i=0;i < result.length; i++) {
  hexString.append(Integer.toHexString((result[i] >>> 4) & 0x0F));
  hexString.append(Integer.toHexString(0x0F & result[i]));
}
Laureenlaurel answered 3/2, 2009 at 22:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.