2
\$\begingroup\$

So I (a newbie to programming) wrote this password-generator as one of my very first programs in Kotlin. I started with Java a few weeks ago. I soon realized that Java was too hard for me, so I switched to Kotlin. And this is the result. A cmd-only password-generator. I'd like to know if this code is useful at all and what I can improve.

fun main() {
    script()
}

fun script() {
    var password: String
    var passwordLength: String
    var passwordAlphabet: String
    var passwordNumbers: String
    var passwordCharsIncluded: String
    var passwordUppercase: String

    println("Password Generator")
    println("Complete the following attributes to generate your password")

    println("length:")
    passwordLength = readln()
    while (passwordLength.toByte() < 6) {
        println("The minimum length is 6 characters, please enter a new length:")
        passwordLength = readln()
    }

    println("allow alphabetic characters?")
    passwordAlphabet = readln()

    println("allow numbers?")
    passwordNumbers = readln()

    println("allow uppercase?")
    passwordUppercase = readln()

    println("include special characters:")
    passwordCharsIncluded = readln()
    if (passwordCharsIncluded == "default") {
        while (passwordLength.toByte() < 32) {
            println("The 'default' option requires a length of at least 32.")
            println("Would you like to change the length?")
            var answer = readln()
            if (answer.equals("yes") || answer.equals("y")) {
                println("Enter new length:")
                passwordLength = readln()
            } else if (answer == "no") {
                println("include special characters:")
                passwordCharsIncluded = readln()
                break
            }
        }
    }

    password = generatePassword(
        passwordLength,
        passwordAlphabet,
        passwordNumbers,
        passwordCharsIncluded,
        passwordUppercase
    )

    if (password.equals("error")) {
        println("An error occurred, please restart the program. ")
    } else {
        println("Your password has been successfully generated:")
        println(password)
    }
}

fun generatePassword(
    passwordLength: String,
    passwordAlphabeticCharacters: String,
    passwordNumbers: String,
    passwordCharsIncluded: String,
    passwordUppercase: String
): String {

    var password: String = ""
    var testPassed: Boolean = true

    var alphabetRange = mutableListOf<Char>()
    var alphabetSelected: Boolean = false
    var numberRange = mutableListOf<Char>()
    var numberSelected: Boolean = false
    var specialCharRange = mutableListOf<Char>()
    var uppercaseSelected: Boolean = false

    var additionalSpecialChars = passwordCharsIncluded.toCharArray()
    var defaultSpecialChars = "^!$%&/()=?+*#_<>".toCharArray()

    // The following if-statements create three lists with the choosen characters.
    if (passwordAlphabeticCharacters.equals("yes") || passwordAlphabeticCharacters.equals("y")) {
        alphabetSelected = true
        for (lowercaseChar in 'a'..'z') {
            alphabetRange.add(lowercaseChar)
        }
        if (passwordUppercase.equals("yes") || passwordUppercase.equals("y")) {
            uppercaseSelected = true
            for (uppercaseChar in 'A'..'Z') {
                alphabetRange.add(uppercaseChar)
            }
        }
        alphabetRange.shuffle()
    }

    if (passwordNumbers.equals("yes") || passwordNumbers.equals("y")) {
        numberSelected = true
        for (num in '0'..'9') {
            numberRange.add(num)
        }
        numberRange.shuffle()
    }

    if (passwordCharsIncluded.equals("default")) {
        for (char in defaultSpecialChars) {
            specialCharRange.add(char)
        }
    } else {
        for (char in additionalSpecialChars) {
            specialCharRange.add(char)
        }
    }

    specialCharRange.shuffle()

    // The password is created by selecting a random char that's being added at the end.
    var counter1: Byte = 0
    while (counter1 < passwordLength.toByte()) {
        if (alphabetSelected && numberSelected) {
            var randomRange = generateRandomNumber(0, 1)
            if (randomRange == 0) {
                var randomIndex = generateRandomNumber(0, alphabetRange.size - 1)
                password += alphabetRange[randomIndex]
            } else if (randomRange == 1) {
                var randomIndex = generateRandomNumber(0, numberRange.size - 1)
                password += numberRange[randomIndex]
            }
        } else if (alphabetSelected) {
            var randomIndex = generateRandomNumber(0, alphabetRange.size - 1)
            password += alphabetRange[randomIndex]
        } else if (numberSelected) {
            var randomIndex = generateRandomNumber(0, numberRange.size - 1)
            password += numberRange[randomIndex]
        }
        counter1++
    }

    if (!passwordCharsIncluded.isBlank()) {
        var tempPassword = password.toCharArray()
        for (i in tempPassword.indices) {
            if (passwordLength.toShort() < 10) {
                var randomNum = generateRandomNumber(0, 2)
                if (randomNum > 1) {
                    var randomIndex = generateRandomNumber(0, specialCharRange.size - 1)
                    tempPassword[i] = specialCharRange[randomIndex]
                }
            } else if (passwordLength.toShort() > 10) {
                var randomNum = generateRandomNumber(0, 3)
                if (randomNum > 1) {
                    var randomIndex = generateRandomNumber(0, specialCharRange.size - 1)
                    tempPassword[i] = specialCharRange[randomIndex]
                }
            }
        }
        password = String(tempPassword)
    }

    // Test if all conditions are given in the final password.
    if (alphabetSelected) {
        val regex = Regex("[a-z]")
        if (!regex.containsMatchIn(password)) {
            testPassed = false
        }
    }

    if (uppercaseSelected) {
        val regex = Regex("[A-Z]")
        if (!regex.containsMatchIn(password)) {
            testPassed = false
        }
    }

    if (numberSelected) {
        val regex = Regex("[0-9]")
        if (!regex.containsMatchIn(password)) {
            testPassed = false
        }
    }

    // The password is being returned if all tests are passed.
    if (testPassed) {
        return password;
    } else {
        return "error";
    }
}

fun generateRandomNumber(startValue: Int, endValue: Int): Int {
    return (startValue..endValue).random()
}
\$\endgroup\$
2
  • \$\begingroup\$ Something like a password generator needs to use a cryptographic rng. I tried to find some mention of what Kotlin is using here but the language is so advanced I couldn't find even a hint in their docs, in contrast to dinosaur Java which very carefully documents the Random classes and subclasses. \$\endgroup\$ Commented Jan 5, 2022 at 20:59
  • 1
    \$\begingroup\$ @PresidentJamesK.Polk See my answer for an adapter \$\endgroup\$ Commented Jan 7, 2022 at 18:26

2 Answers 2

1
\$\begingroup\$

I started with Java a few weeks ago. I soon realized that Java was too hard for me

Excellent choice! Nobody needs Java the dinosaur these days ;-] You'll write code much more efficiently with Kotlin.


I've got one suggestion to offer that will allow you to greatly simplify your application by removing virtually all of the boolean variables when you create an enum like this one:

enum class PasswordOption {
    Letters,
    Numbers,
    UpperCase,
    LowerCase,
    SpecialCharacters
}

You then create only a single set:

var passwordOptions = EnumSet.noneOf(PasswordOption::class.java)

that you update according to user input:

println("allow alphabetic characters?")
if(readln().matches(Regex("y(es)?", RegexOption.IGNORE_CASE))) passwordOptions.add(PasswordOption.Letters)

Notice here the matches extension and the Regex pattern that checks for either yes or y and is case-insensitive at the same time. If the result is true, you add a new flag to the options.


Later inside the generatePassword function you check those flags:

if (passwordOptions.contains(PasswordOption.Letters)) {
    // ...
}
```

\$\endgroup\$
3
\$\begingroup\$

A comment on your question mentions that you should use secure RNG (Random Number Generator) and it's entirely correct. When it comes to secure things such as password, you don't want anyone to be able to detect any patterns in your randnomness. The default randomness in both Java and Kotlin is not secure.

In Java there is a SecureRandom class that we can use. Unfortunately, no such thing exists in Kotlin, but we can create an adapter for it ourselves:

class MySecureRandom : Random() {
    private val secureRandom = SecureRandom()

    override fun nextBits(bitCount: Int): Int {
        val bytes = ByteArray(ceil(bitCount / 8.0).toInt())
        secureRandom.nextBytes(bytes)
        val i = bytes.fold(0) { curr, next ->
            (curr shl 8) + next
        }
        val andValue = if (bitCount == 32) 0xFFFFFFFF else (1 shl bitCount).minus(1)
        return i and andValue.toInt()
    }
}

Warning: This code is not entirely tested and I cannot guarantee that it will work as well as Java's SecureRandom. Among potentially other things, this implementation is slower because it creates a new ByteArray each invocation. This is just my attempt at an adapter.

Then you can use the above implementation in any randomness call. Such as:

val random = MySecureRandom()
list.shuffled(random)
(1..10).random(random)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.