пятница, 4 февраля 2011 г.

Несложный класс для шифрования и дешифрования строки на Java c сохраняемым в программе ключом

С помощью нижеприведенного класса можно легко зашифровать некоторую строку (например пароль) с использование алгоритма DES, и затем при желании расшифровать её. При этом секретный ключ сохранен в теле класса, так что строку можно будет расшифровать даже после перезапуска программы.

// Файл SecuritySettings.java

package security;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

/**
 *
 * @author Cloud
 */


public final class SecuritySettings {

    private final static class MySecretKey implements SecretKey {

        private byte[] key = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; // ключ
      // не должен иметь длину более 8 байт, для безопасного шифрования его
      // необходимо изменить 

        public String getAlgorithm() {
            return "DES";
        }

        public String getFormat() {
            return "RAW";
        }

        public byte[] getEncoded() {
            return key;
        }
    }

    private static SecretKey key;

    private static Cipher ecipher;
    private static Cipher dcipher;

    static {
        try {
            key = new MySecretKey();
            ecipher = Cipher.getInstance("DES");
            dcipher = Cipher.getInstance("DES");
            ecipher.init(Cipher.ENCRYPT_MODE, key);
            dcipher.init(Cipher.DECRYPT_MODE, key);
        } catch (InvalidKeyException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchPaddingException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


    /**
     * Функция шифрования
     * @param str строка открытого текста
     * @return зашифрованная строка в формате Base64
     */
    public static String encrypt(String str) {
        try {
            byte[] utf8 = str.getBytes("UTF8");
            byte[] enc = ecipher.doFinal(utf8);
            return new sun.misc.BASE64Encoder().encode(enc);
        } catch (IllegalBlockSizeException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        } catch (BadPaddingException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    /**
     * Функция расшифрования
     * @param str зашифрованная строка в формате Base64
     * @return расшифрованная строка
     */
    public static String decrypt(String str)  {
        try {
            byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
            byte[] utf8 = dcipher.doFinal(dec);
            return new String(utf8, "UTF8");
        } catch (IllegalBlockSizeException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        } catch (BadPaddingException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(SecuritySettings.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
}


UPD 06.02.13
Выкладываю обновленную версию класса. В новой версии используется сторонняя билиотека Apache Codec http://commons.apache.org/codec/, также методы класса сделаны нестатическими, благодаря чему стало возможным с данным классом создавать несколько шифрователей с разными ключами или алгоритмами в одном приложении и динамически менять ключ шифрования. Минимальная версия jdk - 1.7, однако после внесения небольших изменений может использоваться и на более ранних версиях java.

// Файл StringCrypter.java
package rva.common.util;

import java.io.IOException;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

/**
 * Класс для шифрования и дешифрования строк
 * Использует библиотеку Apache Codec http://commons.apache.org/codec/
 * @author Рудницкий Валентин
 */
public class StringCrypter {

    /**
     * Упрощенный конструктор. Создает StringCrypter с ключом DESSecretKey со значением по умолчанию (не рекомендуется)
     */
    public StringCrypter() {
        this(new byte[]{1, 2, 3, 4, 5, 6, 7, 8});
    }
    
    /**
     * Упрощенный конструктор. Создает StringCrypter с ключом 
     * DESSecretKey (алгоритм шифрования DES) со значением key. 
     * Ключ key должен иметь длину 8 байт
     */
    public StringCrypter(byte[] key) {
        try {
            updateSecretKey(new DESSecretKey(key));
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException ex) {
            throw new IllegalArgumentException(ex.getMessage());
        }
    }

    public StringCrypter(SecretKey key) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            InvalidKeyException {
        updateSecretKey(key);
    }

    private void updateSecretKey(SecretKey key) throws NoSuchPaddingException,
            NoSuchAlgorithmException,
            InvalidKeyException {
        ecipher = Cipher.getInstance(key.getAlgorithm());
        dcipher = Cipher.getInstance(key.getAlgorithm());
        ecipher.init(Cipher.ENCRYPT_MODE, key);
        dcipher.init(Cipher.DECRYPT_MODE, key);
    }

    public static class DESSecretKey implements SecretKey {

        private final byte[] key;

        /**
         * ключ должен иметь длину 8 байт
         */
        public DESSecretKey(byte[] key) {
            this.key = key;
        }

        @Override
        public String getAlgorithm() {
            return "DES";
        }

        @Override
        public String getFormat() {
            return "RAW";
        }

        @Override
        public byte[] getEncoded() {
            return key;
        }
    }

    private Cipher ecipher;
    private Cipher dcipher;

    /**
     * Функция шифрования
     *
     * @param str строка открытого текста
     * @return зашифрованная строка в формате Base64
     */
    public String encrypt(String str) {
        try {
            byte[] utf8 = str.getBytes("UTF8");
            byte[] enc = ecipher.doFinal(utf8);
            return Base64.encodeBase64String(enc);
        } catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException ex) {
            Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    /**
     * Функция дешифрования
     *
     * @param str зашифрованная строка в формате Base64
     * @return расшифрованная строка
     */
    public String decrypt(String str) {
        try {
            byte[] dec = Base64.decodeBase64(str);
            byte[] utf8 = dcipher.doFinal(dec);
            return new String(utf8, "UTF8");
        } catch (IllegalBlockSizeException | BadPaddingException | IOException ex) {
            Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
}


Пример использования нового класса

// создаем экземпляр класса StringCrypter с ключем шифрования
  StringCrypter crypter=new StringCrypter(new byte[]{1,4,5,6,8,9,7,8});
  String testStr = "forCryptString";
// шифрование
  String encBase64Str = crypter.encrypt(testStr);

// дешифрование
  String decryptedStr = crypter.decrypt(encBase64Str);
        

22 комментария:

  1. у меня подчёркивает sun.
    не подскажете в чём дело?

    ОтветитьУдалить
    Ответы
    1. выложил обновленную версию класса, без использования пакета sun

      Удалить
  2. Если я правильно понял - подчеркивает все записи из пакета sun?
    Возможно ваша IDE (среда разработки) сконфигурирована так, чтобы выдавать ошибки компилляции при использовании запрещенных (deprecated) и защищенных (restricted) методов. Пакет sun является защищенным.
    Как вариант решения этой проблемы можете сменить в настройках IDE уровень ошибок для защищенных методов с ошибок компилляциии на предупреждения.
    Либо, что более предпочтительно, использовать стороннюю библиотеку, например Apache Codec http://commons.apache.org/codec/ (org.apache.commons.codec.binary.Base64)для декодирования Base64 формата.

    ОтветитьУдалить
  3. а реализации на PHP нет? Хоца отправить зашифровано постом в пхп.

    ОтветитьУдалить
  4. засунул всё в свой проект в NetBeans IDE 7.1.1
    так он упорно подчеркивает
    catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException ex)
    что делать?

    ОтветитьУдалить
    Ответы
    1. Скорее всего у вас jdk ниже версии 1.7. А подчеркивается использование операторов multucatch, т.е. отлавливание одним блоком catch нескольких исключений, возможность, которая появилась только в 7 версии java.
      Как вариант решения проблемы можете разбить этот оператор на несколько, т.е. заменить

      catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException ex) {
      throw new IllegalArgumentException(ex.getMessage());
      }

      на

      catch (InvalidKeyException ex) {
      throw new IllegalArgumentException(ex.getMessage());
      } catch (NoSuchAlgorithmException ex) {
      throw new IllegalArgumentException(ex.getMessage());
      } catch (NoSuchPaddingException ex) {
      throw new IllegalArgumentException(ex.getMessage());
      }

      Удалить
  5. изменение платформу не решило проблему (1.7.0_04)
    а вот замена помогла только в первом случаи
    ругается по пе прежнему на две строки
    } catch (IllegalBlockSizeException | BadPaddingException | IOException ex) {

    ОтветитьУдалить
    Ответы
    1. Помимо изменения платформы нужно в настройках проекта изменить формат исходного кода на JDK 7
      или
      по аналогии с предыдущим замените
      catch (IllegalBlockSizeException | BadPaddingException | IOException ex) {
      Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
      }

      на

      catch (IllegalBlockSizeException ex) {
      Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
      } catch (BadPaddingException ex) {
      Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
      } catch (IOException ex) {
      Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
      }

      Удалить
  6. ах да огромное пасибо!!!
    и тут одн лишняя скобочка

    StringCrypter crypter=new StringCrypter(new byte[]{1,4,5,6,8,9,7,8});

    ОтветитьУдалить
  7. привет. выкидывает ошибку
    javax.crypto.BadPaddingException: Given final block not properly padded
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
    at com.sun.crypto.provider.DESCipher.engineDoFinal(DESCipher.java:314)
    at javax.crypto.Cipher.doFinal(Cipher.java:1922)
    at ua.kiev.makson.logic.StringCrypter.decrypt(StringCrypter.java:113)
    at ua.kiev.makson.logic.JavaSQL.showAll(JavaSQL.java:53)
    at ua.kiev.makson.logic.Password.start(Password.java:64)
    at ua.kiev.makson.logic.Password.main(Password.java:25)

    ОтветитьУдалить
    Ответы
    1. Привет, судя по тексту ошибки похоже на то что строка которая передана для дешифрования имеет неправильный формат (не является защифрованной строкой, т.е. не была создана методом encrypt)

      Удалить
    2. Постоянно выкидывать эту ошибку. возможно мой трабл связан с тем что я сначала шифрую - добавляю в бд - потом по надобности достаю значение - пытаюсь дешифровать и ОШИБКА.
      если в отдельном классе то все ок.
      в чем может быть трабл?

      Удалить
    3. Сложно сказать, сам класс шифрования должен работать нормально (я проверял :) ), возможно проблема в кодировках, т.е. строка читается из БД не в той кодировке в которой была туда сохранена, или может быть поле таблицы в которое записывается зашифрованная строка имеет длину меньшую чем строка и строка например обрезается, или просто к строке при чтении из БД по каким то причинам добавляются лишние символы.
      Вообще вариантов может быть масса, но вероятно строка которая подается на вход метода decrypt каким то образом отличается от строки которая была возвращена методом encrypt, я бы смотрел в эту сторону. Также желательно убедиться что для шифрования и дешифрования SecretKey используется один и тот же .

      Удалить
  8. Добрый день. 10 секунд это многовато конечно, у меня вроде бы подвисало, но не так сильно, но вообще задержки этого метода это нормально, т.к. операция создания ключа шифрования это довольно тяжелая операция.
    Могу, как вариант, порекомендовать создавать объект класса StringCrypter при старте приложения, и сохранять его в какую нибудь переменную (статическую если класс не синглтон), и затем использовать его многократно для шифрования и дешифрования строк.
    Однако надо учитывать, что класс StringCrypter не потокобезовасный (т.к. используемые им классы Cipher не потокобезовасные), поэтому если у вас доступ к StringCrypter предположительно будет производиться из нескольких потоков одновременно, то необходимо также добавить синхронизацию при шифровании и дешифровании.

    ОтветитьУдалить
  9. Спасибо! Примерно понял проблему.
    Правильно я понимаю, что если идти по пути создания переменной при инициализации, то это значит, что когда в программе этот класс может использоваться в разных частях программы одновременно (например сервер), то могут быть ошибки синхронизации? Если да, то если не сложно, посоветуйте несколько источников, где можно почитать про синхронизацию.

    ОтветитьУдалить
    Ответы
    1. Да, если класс будет использоваться в разных частях программы одновременно в разных потоках, могут появляться ошибки. Насчет источников, в официальной справке оракл очень неплохо написано про синхронизацию, вот пара ссылок https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html.

      Конкретно для StringCrypter, на мой взгляд, проще всего будет добавить слово synchronized к методам encrypt() и decrypt().
      Чуть лучше, но сложнее, будет добавить блок synchronized (cipher) внутри методов encrypt() и decrypt() и сделать поля ecipher и dcipher final, код метода updateSecretKey перенести в конструктор, и сам метод updateSecretKey удалить.

      Удалить
  10. Спасибо за ответ. Очень интересно. Буду пробовать.

    ОтветитьУдалить
  11. При попытке зашифровать строку выдаёт следующую ошибку
    Caused by: java.lang.NoSuchMethodError: No static method encodeBase64String([B)Ljava/lang/String; in class Lorg/apache/commons/codec/binary/Base64; or its super classes (declaration of 'org.apache.commons.codec.binary.Base64' appears in /system/framework/ext.jar)
    С чем это связано и как исправить эту ошибку? (библиотека 'commons-codec-1.11.jar')

    ОтветитьУдалить
    Ответы
    1. Случайно не под Андроид пишете?
      Скорее всего такая ошибка происходит из-за того что в андроид уже включена своя версия commons-codec, которая более старая чем требуется и в которой нет метода encodeBase64String. Попробуйте использовать этот класс: android.util.Base64 вместо org.apache.commons.codec.binary.Base64.

      Метод encrypt скорее всего будет выглядеть примерно так:
      public String encrypt(String str) {
      try {
      byte[] utf8 = str.getBytes("UTF8");
      byte[] enc = ecipher.doFinal(utf8);

      return Base64.encodeToString(enc, Base64.DEFAULT);
      } catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException ex) {
      Logger.getLogger(StringCrypter.class.getName()).log(Level.SEVERE, null, ex);
      }
      return null;
      }

      И еще примерно также придется поменять метод decrypt.

      Удалить

Ярлыки

java (31) оптимизация (7) CPanel (5) svn (5) windows xp (5) ошибка (5) свой сервер (5) файлы и папки (5) gui (4) регулярные выражения (4) jacoco (3) redmine (3) windows (3) автоматизация (3) защита данных (3) резервное копирование (3) сервер (3) JavaScript (2) Oracle SQL (2) adsl модем (2) apache maven (2) apache tomcat (2) coverage (2) dropbox (2) excel (2) firewall (2) netbeans (2) office 2007 (2) samsung (2) system tray (системный трей) (2) xerox (2) вирусы (2) принтер (2) сериализация (2) удаленный рабочий стол (2) HTML (1) JFileChooser (1) MySQL (1) Nokia (1) Ovi (1) P660R-T1 (1) WakeOnLan (1) blogger.com (1) ctfmon.exe (1) email (1) flash память (1) ftp (1) integration testing (1) ip (1) jQuery (1) jvisualWm (1) log4j (1) look and feel (1) myBatis (1) php (1) serialVersionUID (1) skype (1) smtp (1) ssh (1) swing (1) torrents (1) unit-testing (1) unix (1) vpn (1) windows 7 (1) xStarter (1) zip (1) безопасность (1) вход в систему (1) дизайн (1) документация (1) заправка (1) интернет (1) логирование (1) мышь (1) патч (1) перенос (1) печать (1) плагины (1) почтовые сообщения (1) программирование (1) процессы (1) прошивка (1) сеть (1) сеть. ошибка (1) скрытые файлы (1) списки (1) фильтрация (1) фокус (1) часовые пояса (1) шифрование (1) экран (1)