074: Ben Oberkfell talks Fingerprint API

It’s almost a given these days that most phones will have Fingerprint APIs. But how does the hardware actually work? How does the Software work? How does an Android developer make use of these APIs? Ben Oberkfell breaks it down for us in great detail. Listen on!

Download

Show Notes

Sponsors

Contact

Transcript

Donn Felker: Kaushik, I forget: do you have a Nexus 6P or a Pixel?

Kaushik Gopal: I have a Pixel.

DF: Really? Do you like it?

KG: Oh, I love it.

DF: Where’s the fingerprint scanner on that? Is it on the back or the front?

KG: It’s on the back, exactly like the 6P’s.

DF: Do you use it for anything, like unlocking?

KG: I use it for everything. I absolutely love it. It’s one of those technologies where, once you start using it, you can’t live without it. I obviously use it for unlocking my phone. I also use an application called 1Password, which stores all my passwords. Thankfully, after much online harassment, they have implemented fingerprint authentication as well. I even use it at Starbucks through Android Pay. I use it all over the place.

DF: It’s absolutely fantastic. I have a 6P, and it was the same way for me. Now that I’ve started using it, I can’t give it up. I will never again buy a phone that doesn’t have fingerprint support.

That brings up a good point: we have a guest today who should help us dive deeply into this topic.

KG: Absolutely. I think it was at DroidCon NYC where this gentleman gave a talk about Fingerprint ID. We’ve been excited about this interview for a long time, because we’ve been wanting to talk about this. Without further ado, we welcome Ben onto the show.

Ben Oberkfell: Hi! I’m really glad to be here. Thanks for having me on.

DF: Ben, for any folks who aren’t familiar with you (even though you’re pretty active in the community), can you give us a bit of background about yourself and tell us about where you work and how you got into Android?

BO: I live in St. Louis. Until recently, I worked for American Express, where I helped build fingerprint login into their app. Now I work for the New York Times, where I’m building the Android version of the crossword app. In other words, I get paid to do the crosswords, which is really fun!

I got on this journey back in 2010, when the Nexus One came out, and I started goofing around with it. Then this conference called “Google I/O” surfaced on my feed. The registration window was open for 90 days, so I had plenty of time to decide whether I wanted to go or not. It wasn’t a complete free-for-all back then (like it later became). I fell in love with developing on Android through that. I did some side projects here and there while I was working on cancer research, and then wound up going into this full time about four years ago.

DF: That’s cool! Don’t you also work with the Google Developers Group in St. Louis?

BO: Yeah, I’m one of the two co-organizers of the St. Louis GDG.

KG: Is the New York Times crossword app an independent application, or is it part of the New York Times app itself?

BO: It’s a separate app, with a separate subscription—although if you subscribe to the digital news, you’ll get a $20 discount on that. Also, if you want to buy one-off puzzle packs, we have in-app purchases for those. So, if you don’t want to commit to a year, you can buy a set of puzzles to play on the train.

KG: That’s very cool. We’ll add a link to that in the show notes, because I’m a big fan of the New York Times. It’s one of the few newspapers that I try to read regularly.

Ben, Donn and I want to talk about the fingerprint API, because that’s the thing that really excites us, but we want to step back a little. As developers, it makes a lot more sense to us if we understand the hardware as well. Do you think we can start off there? Could you talk about how this whole fingerprint authentication mechanism works at a hardware level, just so we can get a brief understanding before we dive into the actual software, which is typically where we try to do all the magic?

DF: How does the fingerprint hardware on the back of the Nexus 6P, the Pixel, and a bunch of other devices actually work? Do you know?

BO: Some of the scanners that were on some of the older Samsung phones were optical sensors, but today’s sensors are actually capacitative. It’s similar to the sensor your screen uses, but at a much higher resolution. There’s a grid of capacitors, and the grooves, the valleys, and everything else in your fingerprint will affect the current that’s passed through. Reading off that grid basically forms an image—a grid of pixels. That’s how a sensor resolves a fingerprint image without having to use a camera.

KG: I didn’t know that, but it makes so much sense when you describe it that way. I thought it was just a low-end camera that took an image, since I heard that if you scratch it (or simply use your phone for some time, so that it wears out) then it wouldn’t work because the image would not have a high enough resolution to resolve your fingerprint. Obviously, I imagine it still wouldn’t work, but I understand why it happens now. That’s amazing.

BO: Qualcomm has a new sensor that can scan through glass and some metals, and can handle things like wet fingers, so you don’t actually need that big round circle on the back of your phone anymore. There aren’t many devices that have that right now, though. I think it’s just a few from Xiaomi and Le Eco.

KG: That’s interesting. Funnily enough, I bought my dad a Pixel phone recently, because he was due for an upgrade. He mentioned that he had gone to the pool or something, and after he came back, he tried to unlock his phone. It didn’t work. It makes sense now, because I imagine those ridges and valleys took a completely contorted shape as they shriveled up.

BO: The other thing that I’ve found is that if your fingers are wet (or even a little bit damp) that can affect the conductivity that it ends up measuring. If I’ve just washed my hands, even if I’ve dried them off, that can sometimes affect the sensor. Most phones have their issues with that from time to time.

DF: I think I remember hearing something related to fingerprint recognition on Episode 38 of the Android Developers Backstage podcast. They mentioned the Chicken Wing test: they wanted to make sure that Android fingerprint authentication worked so well that if you happened to be eating chicken wings, and you wiped your finger on a napkin quickly and put it on the fingerprint sensor, it would still be able to read it.

BO: I’m imagining them sending test devices out to a Super Bowl party to see if it worked…

DF: Anyway, I’ve heard that the fingerprint reader is tied to something on the actual chip itself. Do you know anything about how that works?

BO: Yes, it’s called a Trusted Execution Environment, and it’s either part of the CPU itself or on a separate chip called a secure element. But ARM CPUs have something called a trust zone, which is almost like a secure system within a system. Code running on Android can’t actually get at the memory used by what’s in the trust zone. If there’s rogue software running on your phone—maybe you have a virus, or maybe you’re rooted and you’re intentionally trying to pick at it—it still can’t exfiltrate what’s going on inside that trust zone.

KG: So you are saying this is almost like a physical hardware component on your phone? Typically, secure phones don’t allow API level access, or even much lower-level access, to any of that information. There’s no reading, just writing. Is that right?

BO: There are channels in and out, but yes: the Android OS kernel can’t actually see what’s going on in there or read that memory. There have been researchers who have tried to break the trust zone barrier, but it’s theoretically a firewall between the potentially insecure Android world and the world of cryptography and fingerprint scanning and matching.

KG: Got it. So there’s something called a trust zone, which I imagine is a marketing thing from the folks at ARM. And the Trusted Execution Environment is basically the genetic term for where the secure information is held. So, all Android phones have a Trusted Execution Environment?

BO: Not every one. This is something that a lot of older Android phones either did not have or make use of.

That’s one thing that you’ll find in the Android Compatibility Definition Document, which is a great piece of bedtime reading.

DF: A cure for insomnia?

BO: Print it out and keep it on the nightstand. Anyway, it’s basically the Bible of what you have to comply with to ship a version of Android that contains the Google Play Store and all of the Google accoutrements that come with an Android phone. One of the things that it specifies is how you should build your hardware if you’re have a fingerprint reader. For instance, fingerprint matching has to happen in trusted hardware, and the fingerprint reader has to have a secure channel straight to that. It can’t go to the user space on the Android side. Also, cryptography (which we’ll get to later today, I imagine) has to happen in that trusted zone as well.

DF: That kind of bridges the gap into the software component, because the hardware and the software have to talk together. This reminds me of when the Nexus 5X and 6P came out. They used what was called “Nexus Imprint”, which was the software/hardware combination that allowed Nexus phones to support fingerprint authentication. Do you know if this something that all devices have to implement? Or is this specific to Nexus devices? I know there are some Samsung devices, like a friend of mine has, that use fingerprint authentication, but is that the same thing?

KG: Donn, anyone who uses a Samsung phone is not your friend.

Just kidding….

DF: No, you’re not. You’re not kidding, and that’s the truth! [laughs] We’ll get into that in a minute, don’t worry.

BO: The Nexus Imprint, as far as I can tell, is really just a brand name for the fingerprint framework that arrived in Android 6. The underlying implementation of that fingerprint experience is what you’ll find in any device that has a fingerprint reader and runs Android 6 or better. Anything that meets these criteria under the CDD (i.e. it gets a fingerprint reader and all of that other good stuff that we talked about) is going to use the Android SDK. It’s effectively the same thing without the fancy branding.

KG: Basically, if I understand this right, the CDD is global to all Android phones, and anyone who implements the CDD allows the fingerprint feature on a phone. And Nexus Imprint is essentially a Google implementation of the CDD, but on the Nexus and Pixel phones, so it’s more like a marketing term.

BO: You got it. There is one wrinkle, though, since we mentioned Samsung. They started using fingerprint readers back with the Note 4 and the Galaxy S5, and their fingerprint API doesn’t do any of the really cool cryptography stuff that the Android API (since Marshmallow) lets you do. It doesn’t actually meet the CDD. If you try to use the fingerprint API on one of these devices (now that they run Marshmallow), Android will report that they don’t actually have fingerprint hardware.

You’ll probably get some 1-star reviews for this. I know I’ve seen them on apps that I’ve used (like 1Password, for instance), and the authors have to go chime in on their support forums, saying, “We’re really sorry, but while these two phones have fingerprint readers, they don’t meet the standards that Google set for this SDK. We can’t use them.” That’s one thing that you’ll have to fight against. Their SDK is simply a call-back of, “Hey, did you get a successful scan or not?” It doesn’t have any of the really fun cryptography stuff that we’ll see in the Android 6 API.

KG: So you’re basically saying that Samsung phones have weak security in their Fingerprint ID, and nobody should ever use Samsung phones ever in their life? And if they get new phones for different people, they should not be buying Samsung phones? That’s basically what you just said, right?

BO: I can’t fully agree with that, but I can say that if you’re going home for the holidays and your family asks, send them in the direction of a Pixel.

KG: Okay, I’m clearly a little biased here.

Anyway, I’m tracking with you so far. All of this makes sense. So, let’s dive into what actually happens once this fingerprint data gets encrypted. I’m curious about how the encryption model works, because (in all seriousness) this is very important. If you’re saying that this is a biometric way of unlocking your phones, and it’s secure, we need to understand how secure it really is, as developers. You also mentioned cryptography. Can you dive a little into that?

BO: One of the things that also comes in the Android CDD (with Android 6 and the fingerprint reader) is what’s called a “hardware keystore”. That’s really just an abstraction around a place where encryption keys can be placed, and where they can’t be exfiltrated out of the system by a rogue app, the government, rooting, or whatever else.

KG: Sorry, can we step back a little? What exactly is a keystore (for those of us who are wondering)?

BO: In Android, when you create an encryption key—either a symmetric key (which can be used to encrypt and decrypt a piece of data), or asymmetric keys (where you retain a private key and give your friends the public key, and vice versa)—it’s saved in what’s called the keystore, which is essentially an abstraction of the place where keys wind up being saved. Some of the older Android devices, way back in the Jelly Bean and Gingerbread dark days, actually didn’t have a place in the hardware where the key material was saved. If you were rooted, all bets were off.

Now, what actually happens is that all of the key manipulation and and actual cryptography happens inside the Trusted Execution Environment.

KG: That’s something we should really hit home. Essentially, you’re saying that after Android M, they began enforcing that encryption should happen only inside this Trusted Execution Environment, so even if you have root access to a phone that runs this operating system that is M and above, then you’re safe, but if you did it before M, then you’re probably not.

BO: So if you have an Android phone that runs M, the private key or symmetric key for any encryption operation will be safely locked up in that vault. It’s never going to be able to leave the friendly confines of the Trusted Execution Environment and get out into the world.

Where that plays in though, is when you create encryption keys on your device. You can actually set a property on those keys that says that authentication via the fingerprint reader is required before anyone can use them. That’s how you can actually use the fingerprint reader to sign data (if you’re using a public/private key pair) or encrypt and decrypt data you have on disk. In either case, you have some assurance that the only way you’re ever going to actually use those keys is if you put your finger to that reader.

DF: This actually could be very useful in situations where you need to provide some type of encryption mechanism for HIPAA compliance, or you need to encrypt a local database. For example, Realm has AES-256 encryption out of the box, but you have provide it a key. You could base it off of a fingerprint, so that if someone wants to have access to your app, and you need to make sure that it’s secure to that particular user, they have to authenticate with a fingerprint to unlock the database.

BO: Since there’s an OS handle for each key, you can just pass that off to whatever API needs the key to do its work.

KG: I have one quick follow-up question. You mentioned that you can set a property on the key that you are encrypting that basically says “user fingerprint required.” The question is: if you don’t have a user fingerprint—say, you only have a pattern or a PIN code—can you only unlock this with a fingerprint?

BO: I haven’t played with it, but as far as I remember, there’s also a way to unlock that key using the passcode, or (effectively) anything else that can open a lockscreen.

KG: Okay. I imagine they’re grouped as a specific sort of thing, because I remember that there was this talk about how Smart Lock is a trust agent thing, but isn’t necessarily as secure. I imagine that there are groups of authentication methods, like “this is super secure” and “this is a convenience kind of security.”

DF: So we talked a little bit about the hardware and some of the higher level software stuff—about how it works and what we can use it for. But if I’m a developer listening to this podcast, and I think, “This is cool. I really have a use for it. I have the HIPAA use case, or maybe I have a financial app that I need to unlock, or something else with security stuff,” what are some of the things that I need to do to implement fingerprint support? Is there a particular library that I need to use?

BO: The first thing that you need to do is make sure that you’re targeting Android 6.0 or better in your app. Once you’ve done that, you should go look in the Android SDK documentation for FingerprintManager. That lits all of the lovely methods that you’re going to use to check if fingerprint hardware exists and whether or not your device has fingerprints added to it, and for performing the authentication itself.

Handily, the support library gives us a class called FingerprintManagerCompat, which wraps a lot of this in SDK-level-safe versions. You don’t need a lot of version checks peppered throughout your code to do FingerprintManager calls. On older versions of Android, this will be a no-op, but on versions that actually support fingerprint, this will call into the FingerprintManager and do the real work.

The first thing you want to do in your UI is query, “Hey, does this phone have the hardware?” Then you could go and present a message to the user: either “You have fingerprints enrolled. Great,” or “I see you have a fingerprint reader. If you go and enroll fingerprints, then you can enable fingerprint authentication in our app.”

KG: Are these standard UI dialogs that come with the support library, or is this something you have to implement?

BO: You’d think that you’d get this for free in the support library, but not so. It’s never this easy with Android. However, if you go to the Google’s Material Design guidelines page, they have a really great page about Fingerprint which gives you some helpful guidelines on how to design your layouts for standard look and feel. They have a standard icon which they’d like you to use across all the apps, so that when you enroll a fingerprint, Android can say, “Hey, look for this icon. When you see it, you can use your fingerprint to unlock this app, or anything else that requires a fingerprint operation.” It also has some really good recaps of what fingerprint flows your user might run into as they’re interacting with the app and the fingerprint reader.

DF: Let me make sure I’m hearing this correctly: there is no built-in dialog, so I have to build my own dialog that looks like Material Design. I have to do all that work if I want to implement this.

BO: Unfortunately, yes. If you go to the Google samples, they have a sample fingerprint app with some layout files that you could pilfer, so that’s always a good start.

One great example that I’ve seen is the Target app. They’ve added fingerprint authentication, but they’ve done a bit of a spin on the dialog. They use more of a flat look, and instead of the white on green Android color, they use white on red. It’s very Target branded, but the Material guidelines do say that you can use your brand colors, as long as you keep that icon somewhere, so people know what to look for across all their interactions with their phone.

DF: Let me take a step back. We’ve talked about FingerprintManager and FingerprintManagerCompat. You can check to see if the hardware is detected, if it has enrolled fingerprints, and all of that good stuff. You can perform different actions given those scenarios, but to take it back to the fantastic Samsung devices that we all love, what happens to some of those older devices with these methods? If there’s a fingerprint reader, is it detected, and are there any weird things that we should expect when working with Samsung fingerprint readers?

BO: The older Samsung phones (like the Galaxy S5 and the Note 4) are going to give you back FALSE if you query for whether fingerprint hardware exists. One thing that I have found in crash logs on those Samsung phones (those are always fun) is that a smattering of devices (and I don’t think I was ever able to resolve what specific type it was) would throw a security exception if there weren’t any fingerprints actually enrolled in the phone.

DF: Thanks, Samsung.

BO: A word of caution from those who have been there before is to definitely wrap stuff like that in handlers, because you never know what Samsung or some other vendor is going to throw at you in this kind of stuff that has deeper hardware interactions.

DF: That’s an excellent bit of advice there. So, if we have the FingerprintManager at this point in time, and we’re ready to implement this, how do we go about generating the keys that we talked about? Myself and a bunch of other folks consider ourselves security dolts, so maybe you could walk us through what that would look like and how to get it set up?

BO: Android has a couple classes that help you create and save keys straight to the keystore. There’s a KeyGenerator, which is what you use for symmetric cryptography (i.e. the same key will encrypt and decrypt a piece of data). Then there’s also a KeyPairGenerator, which lets you generate public/private key pairs for public/private cryptography. The flavor of which classes you’re going to use depends upon what method of cryptography you’re using and what your use case is. For instance, if I’m just going to sign data (rather than encrypt it), I still need to use a key pair generator, because that will allow me to sign data with my private key, and ensure that the only way I can ever sign it is if I put my finger to that reader.

The way we do that is by passing in an instance of a class called a KeyGenParameterSpec. That basically encapsulates all the parameters necessary for creating a key. It comes with a builder, and the builder methods contain things like the algorithm you’re using—you know, like AES and Elliptic Curve—how many bits in the key, etc.

Also, one of those builder methods (after you’ve decided on your algorithm and key complexity) is going to be setUserAuthenticationRequired. This is what puts that key in the vault, so to speak, that’s guarded by the fingerprint reader.

KG: Got it. That’s step one, I suppose: generating the key. You’ve managed to take this key and store it inside the Trusted Execution Environment, but through the API, so you don’t necessarily have to do it yourself.

BO: Yeah. Now, you have to figure out what you want to do with this key. One thing that’s really handy is fingerprint authentication to a remote backend. You can actually sign a piece of data with your private key that authenticates you. Since the only way that I’m able to use that key is by using the fingerprint reader, if I want to prove to a backend server that I put my fingerprint on there, I can sign a piece of data with that private key and the back-end can register it.

For instance, maybe I’m adding fingerprint authentication to an app that has not had it before. In the past, you would sign in with a username and password and get back a session token. Well, you sign a request with your session token and your public key wrapped in that, and send that up to the backend to register your public key as part of the data that’s maintained for you. The next time around, if you send a login request, you don’t send in a username and password. Instead, you send a blob of data that’s signed with that private key, saying, “I want to log in as Donn and I’m signing this with my private key.” Then you can validate that signature on the backend and say, “This passes the signature check by verifying with the public key. I know that he actually has access to that private key, so I can go ahead and log in using that request instead of a username and password.”

What’s really handy is that if I request to enroll my device in this new backend feature, I can give back a device ID that I saved locally. Now, on the backend side, I have the ability to revoke an individual device login. Since I’m not sending a live username and password over the wire anymore, I can remotely disable that device’s ability to login with fingerprint. That gives a little bit more security for your users if they lose their phone, and someone else might have access to the fingerprint.

A really good analogy is if you’ve ever used SSH to login to a server and enrolled your public key on the server side. After that, you basically turbo login. That’s the pretty much exact same thing we’re doing here.

KG: Exactly. That actually brings up two quick follow-up points. First, you mentioned symmetric and asymmetric cryptography off-hand. Just to give folks a quick understanding, symmetric and asymmetric encryption use two different mechanisms. The difference is that symmetric uses the same key to unlock and lock, but asymmetric uses two separate keys to unlock and lock.

BO: Right. With an asymmetric key, you have a private key and a public key. You guard the private key with your life. It’s yours, and yours alone. But you can go and send out your public key anywhere. You could post it on Twitter, or mail it to everyone in the world. In fact, they actually need that key to be able to encrypt data that’s destined for you. So I can go and encrypt data using your public key, and that data is now for your eyes only. Only you can use your private key to unlock it.

What’s also handy is that you can basically use your private key as a digital notary stamp. I can sign a piece of data with my private key, and then (if you have my public key) you can come back and validate that it was really me who signed it.

DF: If folks are really trying hard to wrap their heads around how public/private key encryption works, and if they’ve never really dived into it, it can be a really confusing beast. I’m going to put a link in the show notes to a blog post where the author explains very easily, and through a great analogy, how public/private key encryption works.

KG: My last follow-up question was: you mention sending stuff to your backend. That brings up a question: does fingerprint authentication only work when you’re online? What if I’m offline? Does it not work then? If I’m using an application, and I’m in a tunnel, riding in a bus, does that mean that I can’t use the Touch ID?

BO: Well, if you don’t actually care about having this implemented in your backend side, you can encrypt with a symmetric key to save data offline, and also use that. For instance, maybe I have a diary app. I can use a symmetric key to encrypt every piece of every single journal entry. Maybe I’m using Realm to back this up, as Donn suggested earlier. I could use that locally-saved symmetric key to encrypt some local data and pull it back up later on when I’m offline, or if I don’t want to have any kind of server interaction at all.

KG: Just to wrap this up, we’ve signed the requests with a private key. What happens after that? I have this key, so what do I do now? I imagine I have to use one of the APIs that’s provided to me by Android, and that this is the final step. So what happens? At what point do I get a boolean saying “Yes, you are authenticated,” or “No, you are not authenticated”?

BO: Once you’ve generated this key, you can call authenticate on an instance of the FingerprintManager. You pass in what’s called a CryptoObject, which is really just a generic box to hold a handle to the key. That just tracks whether you’re going to use a key pair or a symmetric key. What that will do is activate the fingerprint reader. When you call this, you also pass in a callback, and you implement a listener. Depending on how that scan went, you’re going to get some calls into this. There’s a success callback that gives you your cryptography object back, blessed by the fingerprint reader. You can pluck your key handle out and go use it to sign or encrypt some data.

Then there are a couple different flavors of error conditions which are handled by two different callbacks that you’ll need to implement. One is for a hard error, where you scan the wrong finger five times and it locks you out. Try it the next time you try to log in to your bank or unlock your lock screen. After five bad tries, it locks you out of the hardware.

The other is a soft error, and there’s a handler for that. Maybe you moved your finger too quickly on the sensor, so it asks you to try again. We’ve all seen that one. There is another one in there that goes back to the hot wing example here: the “The finger is too dirty” error condition that asks someone to use a different finger, go wash their hands, or something.

DF: Take a bath!

BO: What’s really handy about these soft and hard errors is that you don’t actually have to worry too much about disambiguating what the error is. Android is nice enough to pass in a string with these callbacks that has localized error text which you can show in your UI. This might also be because some vendors and OEMs have their own failure cases for why their sensor didn’t work, so they can handle that and you don’t have to worry about it. Or, you won’t have the messages localized to each language, so you don’t need to enumerate localized translations for all of these different error conditions. One thing that you will find in the Material guidelines is that when the framework gives you back those callbacks, don’t try to dream up your own error messages.

KG: That makes sense. Just to wrap up what you just said, you have a cryptography object, which you pass to the FingerprintManager. At that point, the device starts reading your fingerprint. Once the person puts his finger on the sensor, the data comes in, and the callback that you passed in initially when constructing this comes back with a success or failure. Then you can perform accordingly to what you need to do. Is that the high-level overview?

BO: Yep, you’ve got it. If you get a success, you can go and use that key to encrypt or decrypt some data or sign something. At that point, the key is yours to use.

DF: There is a ton of stuff out there about fingerprints. There is the Android documentation and so forth, but I wanted to highlight some of the things that we’ve noticed that you’ve done, Ben. One is that you have an actual Android Fingerprint demo up on Github, correct?

BO: Yeah, I put this together for my talk at DroidCon NYC last fall. What you’ll find in there is a super simple implementation of an app that does fingerprint. I did Uber for baked potatoes, and called it Tuber. You’ll also find a sample backend that handles public key verification. If you want to use a signature based request pattern, you’ll find a starting point in there, so you can pretty easily spin up that backend.

It’s all lovingly done in Kotlin, which you can run locally in the emulator as well, side-by-side. There are some instructions on how to make sure that your emulator can talk to that sample backend, and you can play with Fingerprint there.

DF: Ben, I’m really excited to check out that Github link. I didn’t know there was a server component to it as well, so I’m going to check that out—and fork it, and probably create an Uber for Chipotle, because I need some burritos.

BO: Burrito bowls on the go.

DF: Here’s my money. Take it.

KG: Ben, thank you so much. This has been really exciting. It opens up a whole bunch of possibilities. I’m sure that many folks are curious to see what’s involved in implementing fingerprint authentication, so this is extremely useful stuff. Thank you so much for coming on the show and helping us understand this.

BO: Thank you very much for having me on. I hope this is a good starting point for a lot of people.

KG: Absolutely. If folks want to reach out to you and want to find out more about fingerprints, what’s a good way to do that?

BO: Definitely check out the Github repo in the show notes. If you still have any questions, you can hit me up on Twitter. My handle is @benlikestocode. I’ll be happy to field questions there, for sure.

KG: I know Donn likes to code as well. Where do folks hit you up?

DF: Just like Ben, I’m on Twitter. You can hit me up at @donnfelker. What about you, Kaushik? Where can folks find the illustrious Kaushik Gopal?

KG: Well, I have a very creative Twitter username: @kaushikgopal. You can hit me up there if you have any questions. Thank you all so much for listening, and thanks again, Ben, for coming on this show.