Рубрики
Без рубрики

Практическое руководство № 1. Шифрование данных.

Предисловие Практически каждый разработчик сталкивается с множеством проблем на протяжении своей карьеры. Особенно… Помеченный java, безопасность.

Предисловие

Практически каждый разработчик сталкивается с множеством проблем на протяжении своей карьеры. Особенно если вы работаете над множеством разных проектов и перед вами поставлено множество разнообразных задач. Конечно, большинство из них довольно очевидны и требуют знаний, которые у вас уже есть. Но некоторое их количество требует понимания специфики. Вот почему я начинаю серию постов в блоге с инструкциями. Я хочу поделиться своим практическим опытом работы с фреймворками, базами данных, серверами, облаками и т.д. Я надеюсь, что это поможет кому-то лучше узнать их, тратить меньше времени на размышления о том, как сделать то или иное, и быть более продуктивным в целом.

Каждый пользователь хочет, чтобы его данные были в безопасности. Неважно, идет ли речь о его прогрессе в изучении чего-то в какой-то онлайн-системе или о его деньгах в онлайн-банке. Мы все хотим, чтобы эти данные были защищены. Именно поэтому появились ВВП и другие нормативные акты. Я расскажу, как зашифровать конфиденциальные данные в базе данных SQL, если вы используете Java и такие технологии, как Spring и Hibernate.

Обзор проекта

Это практическое руководство, поэтому давайте создадим небольшой проект, который мы будем использовать для освоения этой темы. Предположим, вы создаете микросервис, который отвечает за хранение пользовательских данных. Не аутентифицировать, а просто хранить все, что относится к конкретному пользователю.

Мотивация

Дата рождения, страна происхождения, адрес и т.д. Все это конфиденциальные данные, которые не подлежат передаче третьим лицам и даже сотрудникам компании, в которой вы работаете. Очевидно, что вам следует ограничить доступ к производственным серверам и базам данных, убедитесь, что ваши API-интерфейсы не позволяют вам получать данные от какого-либо другого лица, но эти меры ограничивают доступ к данным. Сами данные все еще уязвимы – если кто-то получит доступ к базе данных, то у нас будут проблемы. Давайте посмотрим, как мы можем это исправить.

Требования

  1. Данные должны храниться в зашифрованном виде в базе данных.
  2. Шифрование должно происходить при хранении данных в базе данных.
  3. Расшифровка должна происходить каждый раз, когда мы получаем данные из базы данных.

Техническое решение

Существуют различные способы реализации этого. Сейчас мы сосредоточимся на одном конкретном. Для решения будут использоваться конвертеры гибернации и возможности шифрования Java.

Давайте начнем с нашей модели:

@Entity
@Data
@Table(name = "bb_user")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include
    private Long id;

    @Convert(converter = StringAttributeConverter.class)
    private String name;
    private String token;
}

Как вы, скорее всего, заметили, есть аннотация преобразования из пакета javax.persistence. Его роль здесь заключается в том, чтобы запускать преобразователь атрибутов строк именно в те моменты, когда он нам нужен. Вот его исходный код:

@Component
public class StringAttributeConverter implements 
AttributeConverter {
    @Autowired
    private Encryptor encryptor;

    @Override
    public String convertToDatabaseColumn(String attribute) {
        return encryptor.encrypt(attribute);
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
         return encryptor.decrypt(dbData);
    }
}

Метод convertToDatabaseColumn запускается, когда сущность сохраняется в базе данных или обновляется существующая запись. В нашем случае шифратор запускается для шифрования данных. Значение поля, для которого выполняется метод (в нашем случае имя), будет заменено значением, полученным этим методом до завершения транзакции. convertToEntityAttribute метод, очевидно, запускается в тот момент, когда сущность получена из базы данных. Результатом выполнения метода является расшифрованное имя. Репозиторий для этой сущности является простым исполнителем грубого хранилища данных Spring, на уровне репозитория не требуется дополнительная реализация:

public interface UserRepository extends CrudRepository {

    Optional findByToken(String token);
}

Интерфейс шифратора создан на случай, если мы захотим изменить реализацию шифрования:

public interface Encryptor {

    String encrypt(String plainText);

    String decrypt(String encryptedString);
}

Стандартные возможности шифрования Java используются с шифрованием AES для разработчика:

@Component
public class AesEncryptor implements Encryptor {
    private static final Logger LOGGER = 
LoggerFactory.getLogger(AesEncryptor.class);
    private static final String TRANSFORMATION = 
"AES/ECB/PKCS5Padding";
    private static final String UTF_8 = "UTF-8";

    @Autowired
    private SecretKeySpec secretKey;

    @Override
    public String encrypt(String plainText) {
        LOGGER.info("Encrypted field {}", plainText);

        try {
            Cipher cipher = 
Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            Base64.Encoder encoder = Base64.getEncoder();
            byte[] stringBytes = plainText.getBytes(UTF_8);
            return 
 encoder.encodeToString(cipher.doFinal(stringBytes));
        } catch (Exception e) {
            throw new RuntimeException("Could not handle the encryption");
        }
    }

    @Override
    public String decrypt(String encryptedString) {
        LOGGER.info("Decrypted field {}", encryptedString);
        try {
            Cipher cipher = 
Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            Base64.Decoder decoder = Base64.getDecoder();
            return new 
String(cipher.doFinal(decoder.decode(encryptedString)));
        } catch (Exception e) {
            throw new RuntimeException("Could not handle the decryption", e); 
        }
    }
}

И последняя часть здесь – секретный ключ, который используется для процесса шифрования/дешифрования. Вот класс конфигурации, который включает в себя создание компонента с секретным ключом:

@Configuration
public class ApplicationConfiguration {

    @Value("${encryption.key}")
    private String encryptionKey;

    @Bean
    public SecretKeySpec secretKey() throws 
UnsupportedEncodingException, NoSuchAlgorithmException {
        byte[] keyBytes = encryptionKey.getBytes("UTF-8");
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        keyBytes = sha.digest(keyBytes);
        keyBytes = Arrays.copyOf(keyBytes, 16);
        return new SecretKeySpec(keyBytes, "AES");
    }
}

Полный исходный код доступен в репозитории GitHub

Оригинал: “https://dev.to/baseblocks/how-to-1-data-encryption-548m”