|
import org.apache.commons.csv.CSVFormat |
|
import java.io.FileReader |
|
import java.net.HttpURLConnection |
|
import java.net.URL |
|
import java.security.MessageDigest |
|
|
|
fun main(args: Array<String>) { |
|
val allCredentials = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(FileReader("src/main/resources/temp.csv")) |
|
.filter { row -> !row["password"].isNullOrBlank() } |
|
.map { row -> LastPassSite(row["name"], row["url"], hash(row["password"])) } |
|
|
|
val credentialsCount = allCredentials.size |
|
|
|
println("There are $credentialsCount credentials to check") |
|
|
|
allCredentials.withIndex() |
|
.mapNotNull { (index, credentials) -> |
|
println("Checking credentials ${index + 1} of $credentialsCount") |
|
val response = httpGet( |
|
"https://api.pwnedpasswords.com/range/${credentials.passwordHashStart}", |
|
"Kotlin Lastpass password checker - https://gist.github.com/rupert654/6219acc49045b71670af99b83cb6c795" |
|
) |
|
|
|
val breach = response.body |
|
.lines() |
|
.map { it.split(':') } |
|
.map { Breach(it[0], it[1]) } |
|
.firstOrNull { "${credentials.passwordHashStart}${it.passwordHash}" == credentials.passwordHash } |
|
if (breach == null) null else BreachedCredentials(credentials, breach) |
|
} |
|
.forEach(::println) |
|
} |
|
|
|
data class HttpResponse( |
|
val code: Int, |
|
val body: String |
|
) |
|
|
|
data class Breach( |
|
val passwordHash: String, |
|
val count: String |
|
) |
|
|
|
data class BreachedCredentials( |
|
val credentials: LastPassSite, |
|
val breach: Breach |
|
) { |
|
override fun toString(): String { |
|
return "Password for $credentials breached ${breach.count} times." |
|
} |
|
} |
|
|
|
data class LastPassSite( |
|
val name: String, |
|
val url: String, |
|
val passwordHash: String |
|
) { |
|
val passwordHashStart = passwordHash.substring(0..4) |
|
|
|
override fun toString(): String { |
|
var result = "" |
|
if (name.isNotBlank()) { |
|
result += name |
|
} |
|
if (url.isNotBlank()) { |
|
result += " ($url)" |
|
} |
|
return result |
|
} |
|
} |
|
|
|
fun hash(value: String): String { |
|
val hexChars = "0123456789ABCDEF" |
|
val bytes = MessageDigest |
|
.getInstance("SHA-1") |
|
.digest(value.toByteArray()) |
|
val result = StringBuilder(bytes.size * 2) |
|
|
|
bytes.forEach { |
|
val i = it.toInt() |
|
result.append(hexChars[i shr 4 and 0x0f]) |
|
result.append(hexChars[i and 0x0f]) |
|
} |
|
|
|
return result.toString() |
|
} |
|
|
|
fun httpGet(url: String, userAgent: String): HttpResponse { |
|
val connection = URL(url).openConnection() as HttpURLConnection |
|
connection.addRequestProperty("User-Agent", userAgent) |
|
connection.connect() |
|
|
|
val responseCode = connection.responseCode |
|
if (responseCode != 200) { |
|
throw IllegalStateException("Error getting URL $url with response code $responseCode") |
|
} |
|
|
|
return HttpResponse(responseCode, connection.inputStream.use { it.reader().use { reader -> reader.readText() } }) |
|
} |
|
|