Reusable solution: to have an easy way to select which fields to include in equals() and hashCode(), I wrote a little helper called "stem" (essential core data, relevant for equality).
Usage is straightforward, and the resulting code very small:
class Person(val id: String, val name: String) {
    private val stem = Stem(this, { id })
    override fun equals(other: Any?) = stem.eq(other)
    override fun hashCode() = stem.hc()
}
It's possible to trade off the backing field stored in the class with extra computation on-the-fly:
    private val stem get() = Stem(this, { id })
Since Stem takes any function, you are free to specify how the equality is computed. For more than one field to consider, just add one lambda expression per field (varargs):
    private val stem = Stem(this, { id }, { name })
Implementation:
class Stem<T : Any>(
        private val thisObj: T,
        private vararg val properties: T.() -> Any?
) {     
    fun eq(other: Any?): Boolean {
        if (thisObj === other)
            return true
        if (thisObj.javaClass != other?.javaClass)
            return false
        // cast is safe, because this is T and other's class was checked for equality with T
        @Suppress("UNCHECKED_CAST") 
        other as T
        return properties.all { thisObj.it() == other.it() }
    }
    fun hc(): Int {
        // Fast implementation without collection copies, based on java.util.Arrays.hashCode()
        var result = 1
        for (element in properties) {
            val value = thisObj.element()
            result = 31 * result + (value?.hashCode() ?: 0)
        }
        return result
    }
    @Deprecated("Not accessible; use eq()", ReplaceWith("this.eq(other)"), DeprecationLevel.ERROR)
    override fun equals(other: Any?): Boolean = 
        throw UnsupportedOperationException("Stem.equals() not supported; call eq() instead")
    @Deprecated("Not accessible; use hc()", ReplaceWith("this.hc(other)"), DeprecationLevel.ERROR)
    override fun hashCode(): Int = 
        throw UnsupportedOperationException("Stem.hashCode() not supported; call hc() instead")
}
In case you're wondering about the last two methods, their presence makes the following erroneous code fail at compile time:
override fun equals(other: Any?) = stem.equals(other)
override fun hashCode() = stem.hashCode()
The exception is merely a fallback if those methods are invoked implicitly or through reflection; can be argued if it's necessary.
Of course, the Stem class could be further extended to include automatic generation of toString() etc.