WIP desktop client for Chatto reimplemented in ScalaFX and Sapphire Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
3.6 KiB

  1. package wow.doge.chatto.service
  2. import wow.doge.chatto.model.MessageCipher;
  3. import java.util.Base64;
  4. import javax.crypto.Cipher;;
  5. import javax.crypto.SecretKeyFactory;
  6. import javax.crypto.spec.GCMParameterSpec;
  7. import javax.crypto.spec.PBEKeySpec;
  8. import javax.crypto.spec.SecretKeySpec;
  9. import java.security.SecureRandom
  10. import scala.util.Try
  11. class EncryptionServiceImpl extends EncryptionService {
  12. override def encrypt(
  13. password: String,
  14. plainText: String
  15. ): String Either MessageCipher = {
  16. val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
  17. val secureRandom = new SecureRandom()
  18. val saltLength = 12
  19. val keyLength = 128
  20. val iterationCount = 10000
  21. val tagSize = 128
  22. val encode = (bytes: Array[Byte]) =>
  23. Base64.getEncoder().encodeToString(bytes)
  24. val salt = Array(saltLength.toByte)
  25. secureRandom.nextBytes(salt)
  26. val nonce = Array(12.toByte)
  27. secureRandom.nextBytes(nonce)
  28. // val spec =
  29. // new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength)
  30. // val tmp = factory.generateSecret(spec)
  31. // val secretKey = new SecretKeySpec(tmp.getEncoded(), "AES")
  32. // val cipher = Cipher.getInstance("AES/GCM/NoPadding")
  33. // cipher.init(
  34. // Cipher.ENCRYPT_MODE,
  35. // secretKey,
  36. // new GCMParameterSpec(128, nonce)
  37. // )
  38. // val cipherTextByte = cipher.doFinal(plainText.getBytes)
  39. val messageCipher = for {
  40. factory <- {
  41. Try(SecretKeyFactory.getInstance("PBKDFWithHmacSHA56")).toOption
  42. .toRight("Failed to get skf instance")
  43. }
  44. spec <- {
  45. Try(
  46. new PBEKeySpec(
  47. password.toCharArray(),
  48. salt,
  49. iterationCount,
  50. keyLength
  51. )
  52. ).toOption.toRight("Failed to get pbekeyspec")
  53. }
  54. secret <- Try(factory.generateSecret(spec)).toOption
  55. .toRight("Failed to get secret")
  56. secretKey <- Try(new SecretKeySpec(secret.getEncoded(), "AES")).toOption
  57. .toRight("Failed to get secret key")
  58. cipher <- Try(Cipher.getInstance("AES/GCM/NoPadding")).toOption
  59. .toRight("Failed to get cipher instance")
  60. _ <- Right(
  61. cipher.init(
  62. Cipher.ENCRYPT_MODE,
  63. secretKey,
  64. new GCMParameterSpec(128, nonce)
  65. )
  66. )
  67. cipherTextByte <- Try(cipher.doFinal(plainText.getBytes())).toOption
  68. .toRight("Failed to generate cipher")
  69. messageCipher = MessageCipher(
  70. v = 1,
  71. salt = encode(salt),
  72. mode = "gcm",
  73. iterations = iterationCount,
  74. cipher = "aes",
  75. adata = "",
  76. cipherText = encode(cipherTextByte),
  77. iv = encode(nonce),
  78. keySize = keyLength,
  79. tagSize = tagSize
  80. )
  81. } yield (messageCipher)
  82. messageCipher
  83. }
  84. override def decrypt(
  85. password: String,
  86. messageCipher: MessageCipher
  87. ): Try[String] = {
  88. val decode = (text: String) => {
  89. Base64.getDecoder().decode(text)
  90. }
  91. Try {
  92. val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
  93. val spec = new PBEKeySpec(
  94. password.toCharArray(),
  95. decode(messageCipher.salt),
  96. messageCipher.iterations,
  97. messageCipher.keySize
  98. );
  99. val tmp = factory.generateSecret(spec);
  100. val secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
  101. val cipher = Cipher.getInstance("AES/GCM/NoPadding")
  102. cipher.init(
  103. Cipher.DECRYPT_MODE,
  104. secretKey,
  105. new GCMParameterSpec(128, decode(messageCipher.iv))
  106. );
  107. new String(cipher.doFinal(decode(messageCipher.cipherText)));
  108. }
  109. }
  110. }
  111. object EncryptionServiceImpl {
  112. def apply() = new EncryptionServiceImpl()
  113. }