Rohan Sircar
4 years ago
16 changed files with 408 additions and 177 deletions
-
17build.sbt
-
1project/plugin.sbt
-
2src/main/resources/application.conf
-
10src/main/resources/styles/chat.css
-
16src/main/scala/wow/doge/chatto/ApplicationController.scala
-
5src/main/scala/wow/doge/chatto/control/MessageBox.scala
-
1src/main/scala/wow/doge/chatto/control/UserBox.scala
-
1src/main/scala/wow/doge/chatto/control/UserBox2.scala
-
192src/main/scala/wow/doge/chatto/controller/ChatController.scala
-
11src/main/scala/wow/doge/chatto/controller/LoginController.scala
-
7src/main/scala/wow/doge/chatto/controller/MainViewController.scala
-
58src/main/scala/wow/doge/chatto/model/Message.scala
-
30src/main/scala/wow/doge/chatto/model/MessageCipher.scala
-
11src/main/scala/wow/doge/chatto/service/EncryptionService.scala
-
135src/main/scala/wow/doge/chatto/service/EncryptionServiceImpl.scala
-
88src/main/scala/wow/doge/chatto/service/UserService.scala
@ -0,0 +1,58 @@ |
|||||
|
package wow.doge.chatto.model |
||||
|
|
||||
|
import java.time.Instant |
||||
|
import com.sandec.mdfx.MDFXNode |
||||
|
import javafx.scene.layout.HBox |
||||
|
import scalafx.Includes._ |
||||
|
import javafx.scene.layout.Priority |
||||
|
import javafx.geometry.Pos |
||||
|
import wow.doge.chatto.model.MessageType.Sender |
||||
|
import wow.doge.chatto.model.MessageType.Receiver |
||||
|
|
||||
|
final case class Message( |
||||
|
fromUser: String, |
||||
|
toUser: String, |
||||
|
message: String, |
||||
|
messageTime: Instant |
||||
|
) |
||||
|
object Message { |
||||
|
lazy val markdownStyleSheet = |
||||
|
getClass().getResource("/styles/markdown.css").toExternalForm() |
||||
|
|
||||
|
def empty = Message("", "", "", Instant.MIN) |
||||
|
|
||||
|
def createMdMessageBox( |
||||
|
message: Message, |
||||
|
messageType: MessageType |
||||
|
) = { |
||||
|
val mdfxNode = new MDFXNode(message.message); |
||||
|
mdfxNode |
||||
|
.getStylesheets() |
||||
|
.add(markdownStyleSheet) |
||||
|
mdfxNode.setMaxWidth(500) |
||||
|
mdfxNode.vgrow = Priority.ALWAYS |
||||
|
mdfxNode.setAlignment(Pos.CENTER) |
||||
|
mdfxNode.styleClass = messageType match { |
||||
|
case Sender => Seq("chat-message-sender-box") |
||||
|
case Receiver => Seq("chat-message-receiver-box") |
||||
|
} |
||||
|
|
||||
|
val box = new HBox() |
||||
|
messageType match { |
||||
|
case Receiver => box.setAlignment(Pos.CENTER_LEFT) |
||||
|
case Sender => box.setAlignment(Pos.CENTER_RIGHT) |
||||
|
} |
||||
|
// box.maxWidth(500) |
||||
|
box.hgrow = Priority.ALWAYS |
||||
|
box.vgrow = Priority.ALWAYS |
||||
|
box.children ++= Seq(mdfxNode) |
||||
|
box.fillHeight = true |
||||
|
box |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
sealed trait MessageType |
||||
|
object MessageType { |
||||
|
case object Sender extends MessageType |
||||
|
case object Receiver extends MessageType |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
package wow.doge.chatto.model |
||||
|
|
||||
|
import org.json4s.FieldSerializer |
||||
|
import org.json4s.FieldSerializer._ |
||||
|
|
||||
|
case class MessageCipher( |
||||
|
iv: String, |
||||
|
v: Int, |
||||
|
iterations: Int, |
||||
|
keySize: Int, |
||||
|
tagSize: Int, |
||||
|
mode: String, |
||||
|
adata: String, |
||||
|
cipher: String, |
||||
|
salt: String, |
||||
|
cipherText: String |
||||
|
) |
||||
|
|
||||
|
object MessageCipher { |
||||
|
val rename = FieldSerializer[MessageCipher]( |
||||
|
renameTo("iterations", "iter") orElse |
||||
|
renameTo("keySize", "ks") orElse |
||||
|
renameTo("tagSize", "ts") orElse |
||||
|
renameTo("cipherText", "ct"), |
||||
|
renameFrom("iter", "iterations") orElse |
||||
|
renameFrom("ks", "keySize") orElse |
||||
|
renameFrom("ts", "tagSize") orElse |
||||
|
renameFrom("ct", "cipherText") |
||||
|
) |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
package wow.doge.chatto.service |
||||
|
|
||||
|
import wow.doge.chatto.model.MessageCipher |
||||
|
import scala.util.Try |
||||
|
|
||||
|
trait EncryptionService { |
||||
|
|
||||
|
def encrypt(password: String, plainText: String): String Either MessageCipher |
||||
|
def decrypt(password: String, cipher: MessageCipher): Try[String] |
||||
|
|
||||
|
} |
@ -0,0 +1,135 @@ |
|||||
|
package wow.doge.chatto.service |
||||
|
|
||||
|
import wow.doge.chatto.model.MessageCipher; |
||||
|
import java.util.Base64; |
||||
|
|
||||
|
import javax.crypto.Cipher;; |
||||
|
import javax.crypto.SecretKeyFactory; |
||||
|
import javax.crypto.spec.GCMParameterSpec; |
||||
|
import javax.crypto.spec.PBEKeySpec; |
||||
|
import javax.crypto.spec.SecretKeySpec; |
||||
|
import java.security.SecureRandom |
||||
|
import scala.util.Try |
||||
|
|
||||
|
class EncryptionServiceImpl extends EncryptionService { |
||||
|
|
||||
|
override def encrypt( |
||||
|
password: String, |
||||
|
plainText: String |
||||
|
): String Either MessageCipher = { |
||||
|
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") |
||||
|
val secureRandom = new SecureRandom() |
||||
|
val saltLength = 12 |
||||
|
val keyLength = 128 |
||||
|
val iterationCount = 10000 |
||||
|
val tagSize = 128 |
||||
|
|
||||
|
val encode = (bytes: Array[Byte]) => |
||||
|
Base64.getEncoder().encodeToString(bytes) |
||||
|
|
||||
|
val salt = Array(saltLength.toByte) |
||||
|
secureRandom.nextBytes(salt) |
||||
|
val nonce = Array(12.toByte) |
||||
|
secureRandom.nextBytes(nonce) |
||||
|
|
||||
|
// val spec = |
||||
|
// new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength) |
||||
|
|
||||
|
// val tmp = factory.generateSecret(spec) |
||||
|
// val secretKey = new SecretKeySpec(tmp.getEncoded(), "AES") |
||||
|
// val cipher = Cipher.getInstance("AES/GCM/NoPadding") |
||||
|
// cipher.init( |
||||
|
// Cipher.ENCRYPT_MODE, |
||||
|
// secretKey, |
||||
|
// new GCMParameterSpec(128, nonce) |
||||
|
// ) |
||||
|
|
||||
|
// val cipherTextByte = cipher.doFinal(plainText.getBytes) |
||||
|
|
||||
|
val messageCipher = for { |
||||
|
factory <- { |
||||
|
Try(SecretKeyFactory.getInstance("PBKDFWithHmacSHA56")).toOption |
||||
|
.toRight("Failed to get skf instance") |
||||
|
} |
||||
|
|
||||
|
spec <- { |
||||
|
Try( |
||||
|
new PBEKeySpec( |
||||
|
password.toCharArray(), |
||||
|
salt, |
||||
|
iterationCount, |
||||
|
keyLength |
||||
|
) |
||||
|
).toOption.toRight("Failed to get pbekeyspec") |
||||
|
} |
||||
|
|
||||
|
secret <- Try(factory.generateSecret(spec)).toOption |
||||
|
.toRight("Failed to get secret") |
||||
|
|
||||
|
secretKey <- Try(new SecretKeySpec(secret.getEncoded(), "AES")).toOption |
||||
|
.toRight("Failed to get secret key") |
||||
|
|
||||
|
cipher <- Try(Cipher.getInstance("AES/GCM/NoPadding")).toOption |
||||
|
.toRight("Failed to get cipher instance") |
||||
|
_ <- Right( |
||||
|
cipher.init( |
||||
|
Cipher.ENCRYPT_MODE, |
||||
|
secretKey, |
||||
|
new GCMParameterSpec(128, nonce) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
cipherTextByte <- Try(cipher.doFinal(plainText.getBytes())).toOption |
||||
|
.toRight("Failed to generate cipher") |
||||
|
|
||||
|
messageCipher = MessageCipher( |
||||
|
v = 1, |
||||
|
salt = encode(salt), |
||||
|
mode = "gcm", |
||||
|
iterations = iterationCount, |
||||
|
cipher = "aes", |
||||
|
adata = "", |
||||
|
cipherText = encode(cipherTextByte), |
||||
|
iv = encode(nonce), |
||||
|
keySize = keyLength, |
||||
|
tagSize = tagSize |
||||
|
) |
||||
|
} yield (messageCipher) |
||||
|
|
||||
|
messageCipher |
||||
|
} |
||||
|
|
||||
|
override def decrypt( |
||||
|
password: String, |
||||
|
messageCipher: MessageCipher |
||||
|
): Try[String] = { |
||||
|
val decode = (text: String) => { |
||||
|
Base64.getDecoder().decode(text) |
||||
|
} |
||||
|
|
||||
|
Try { |
||||
|
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") |
||||
|
val spec = new PBEKeySpec( |
||||
|
password.toCharArray(), |
||||
|
decode(messageCipher.salt), |
||||
|
messageCipher.iterations, |
||||
|
messageCipher.keySize |
||||
|
); |
||||
|
val tmp = factory.generateSecret(spec); |
||||
|
val secretKey = new SecretKeySpec(tmp.getEncoded(), "AES"); |
||||
|
|
||||
|
val cipher = Cipher.getInstance("AES/GCM/NoPadding") |
||||
|
cipher.init( |
||||
|
Cipher.DECRYPT_MODE, |
||||
|
secretKey, |
||||
|
new GCMParameterSpec(128, decode(messageCipher.iv)) |
||||
|
); |
||||
|
new String(cipher.doFinal(decode(messageCipher.cipherText))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
object EncryptionServiceImpl { |
||||
|
def apply() = new EncryptionServiceImpl() |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue