安全性:将javax.mail.Message写入带加密的文件

时间:2016-08-08 14:23:53

标签: java email security encryption

我有一个应该写入文件系统的'javax.mail.Message'对象。我使用版本1.5.5的javax.mail(com.sun.mail)

javax.mail.Message message = buildMessage(...);
message.writeTo(new FileOutputStream("message.plain"));

现在消息被写入文件。但我怎样才能加密&解密这个文件?我在下面发布了我的示例,但此代码失败。

我的示例代码:

static byte[] salt = new byte[8];
static {
    SecureRandom random = new SecureRandom();
    random.nextBytes(salt);
}

public final void test() throws Exception {
    Message message = buildTestMessage(...);

    SecretKey secret = encode(message, new FileOutputStream("test.encrypted"), "01234567".toCharArray());
    Message message2 = decode(new FileInputStream("test.encrypted"), "01234567".toCharArray());

    // out sth. like from@domain <--> null
    System.out.println(message.getFrom()[0] + " <--> " + message2.getFrom());

}

private Message decode(
        InputStream mailFileInputStream, char[] password) throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, MessagingException, IOException, InvalidKeySpecException {
    /* Derive the key, given password and salt. */
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
    KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
    SecretKey tmp = factory.generateSecret(spec);
    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "PBEWithHmacSHA256AndAES_128");

    Cipher cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_128");
    cipher.init(Cipher.DECRYPT_MODE, secret);       

    InputStream is = new CipherInputStream(mailFileInputStream, cipher);
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);

    Message message = new MimeMessage(session, is );
    return message;
}

static SecretKey encode(
        Message message,
        FileOutputStream out,
        char[] password) throws Exception {

    /* Derive the key, given password and salt. */
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
    KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
    SecretKey tmp = factory.generateSecret(spec);
    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "PBEWithHmacSHA256AndAES_128");

    Cipher cipher  = Cipher.getInstance("PBEWithHmacSHA256AndAES_128");
    cipher.init(Cipher.ENCRYPT_MODE, secret);

    OutputStream cos = new CipherOutputStream(out, cipher);
    message.writeTo(cos);
    cos.close();

    return secret;
}

补充:Upvote我的问题,我将你的答案标记为已接受。我有一个问题禁令,没有任何未解决的问题或没有关注我的问题!

1 个答案:

答案 0 :(得分:1)

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class Stackoverflow {

    private static final int keySize = 128;
    static byte[] saltForThisRun = new byte[8];
    static {
        SecureRandom random = new SecureRandom();
        random.nextBytes(saltForThisRun);
    }

    public static void main(
            String[] args) throws Exception {
        Message message = buildTestMessage("from@null.org", new String[] { "to@null.org", "to2@null.org" },
                "Subject is needed here", Optional.of("This is just a text"), Optional.empty());

        char[] password = "password".toCharArray();
        String encryptedMsg = encrypt(password, saltForThisRun, message);
        System.out.println("encryptedMsg: " + encryptedMsg);
        Message message2 = decrypt(password, saltForThisRun, encryptedMsg);

        // out sth. like from@domain <--> null
        System.out.println(message.getFrom()[0] + " <--> " + message2.getFrom()[0]);

    }

    private static Message decrypt(
            char[] password,
            byte[] salt,
            String base64CipherText) throws EncryptionException {
        try {
            SecretKey secretKey = generateKey(password, salt, keySize);
            byte[] initializationVector = createInitializationVector(keySize);

            Cipher aesCipherForDecryption = Cipher.getInstance("AES/CBC/PKCS5PADDING");

            aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));
            byte[] decryptedByteCipherText = Base64.getDecoder().decode(base64CipherText);
            byte[] byteDecryptedMessage = aesCipherForDecryption.doFinal(decryptedByteCipherText);
            return createMessageFromBytes(byteDecryptedMessage);
        } catch (IllegalStateException | InvalidKeyException | InvalidAlgorithmParameterException
                | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeySpecException
                | IllegalBlockSizeException | BadPaddingException | MessagingException e) {
            throw new EncryptionException(String.format("Unable to enrypt message due to: %s", e.getMessage()), e);
        }
    }

    /**
     * 
     * @param password
     * @param salt
     * @param message
     * @param charArray
     * @return Base64 encoded CipherText
     * @throws EncryptionException
     *             If sth. goes wrong
     */
    private static String encrypt(
            char[] password,
            byte[] salt,
            Message message) throws EncryptionException {
        try {
            /**
             * Step 1. Generate an AES key with password and salt
             * 
             */
            SecretKey secretKey = generateKey(password, salt, keySize);
            System.out.println(secretKey.getEncoded().length);

            /**
             * Step 2. Generate an Initialization Vector (IV)
             * a. Use SecureRandom to generate random bits
             * The size of the IV matches the blocksize of the cipher (e.g. 128 bits for AES)
             * b. Construct the appropriate IvParameterSpec object for the data to pass to Cipher's init() method
             */
            byte[] initializationVector = createInitializationVector(keySize);

            /**
             * Step 3. Create a Cipher by specifying the following parameters
             * a. Algorithm name - here it is AES
             * b. Mode - here it is CBC mode
             * c. Padding - e.g. PKCS7 or PKCS5
             * 
             * Must specify the mode explicitly as most JCE providers default to ECB mode!!
             */
            Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5PADDING");

            /**
             * Step 4. Initialize the Cipher for Encryption
             */
            aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));

            /**
             * Step 5. Encrypt the Data
             * a. Declare / Initialize the Data. Here the data is of type String
             * b. Convert the Input Text to Bytes
             * c. Encrypt the bytes using doFinal method
             */
            byte[] byteCipherText = updateCipherWithMessage(aesCipherForEncryption, message);
            return new String(Base64.getEncoder().encode(byteCipherText));

        } catch (IllegalBlockSizeException | BadPaddingException | IOException | MessagingException
                | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException
                | NoSuchPaddingException | InvalidKeySpecException e) {
            throw new EncryptionException(String.format("Unable to enrypt message due to: %s", e.getMessage()), e);
        }

    }

    private static MimeMessage createMessageFromBytes(
            byte[] decodedBytes) throws MessagingException {
        InputStream is = new ByteArrayInputStream(decodedBytes);
        Session session = null;
        return new MimeMessage(session, is);
    }

    private static byte[] updateCipherWithMessage(
            Cipher cipher,
            Message message) throws IOException, MessagingException, IllegalBlockSizeException, BadPaddingException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            message.writeTo(os);
            return cipher.doFinal(os.toByteArray());
        } finally {
            os.close();
        }
    }

    private static byte[] createInitializationVector(
            int keyLength) {
        // Save the IV bytes or send it in plaintext with the encrypted data so you can decrypt the data later
        byte[] iv = new byte[keyLength / 8];

        SecureRandom prng = new SecureRandom();
        prng.nextBytes(iv);
        return iv;
    }

    private static SecretKey generateKey(
            char[] password,
            byte[] salt,
            int keySize) throws InvalidKeySpecException, NoSuchAlgorithmException {
        int nIterations = 65536;
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(password, salt, nIterations, keySize);
        SecretKey tmp = factory.generateSecret(spec);
        return new SecretKeySpec(tmp.getEncoded(), "AES");
    }

    private static Message buildTestMessage(
            String from,
            String[] to,
            String subject,
            Optional<String> text,
            Optional<String> html) throws Exception {
        String replyto = from;
        Session session = null;
        Message message = new MimeMessage(session);
        Multipart multiPart = new MimeMultipart("alternative");

        try {

            if (text.isPresent()) {
                MimeBodyPart textPart = new MimeBodyPart();
                textPart.setText(text.get(), "utf-8");
                multiPart.addBodyPart(textPart);
            }
            if (html.isPresent()) {
                MimeBodyPart htmlPart = new MimeBodyPart();
                htmlPart.setContent(html, "text/html; charset=utf-8");
                multiPart.addBodyPart(htmlPart);
            }

            message.setContent(multiPart);

            if (from != null) {
                message.setFrom(new InternetAddress(from));
            } else
                message.setFrom();

            if (replyto != null)
                message.setReplyTo(new InternetAddress[] { new InternetAddress(replyto) });
            else
                message.setReplyTo(new InternetAddress[] { new InternetAddress(from) });

            InternetAddress[] toAddresses = new InternetAddress[to.length];
            for (int i = 0; i < toAddresses.length; i++) {
                toAddresses[i] = new InternetAddress(to[i]);
            }
            message.setRecipients(Message.RecipientType.TO, toAddresses);
            message.setSubject(subject);
            message.setSentDate(new Date());

            return message;

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("Exception: " + e.getMessage());
            throw e;

        }

    }

}