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