Unlocking a Password Vault Based on Who You Are, Not What You Have

Security for your digital life is more important than ever. Passwords have been common up until now, but identity and authentication methods are moving away from usernames and passwords. They require users to sacrifice convenience for security and add friction to the overall experience — and many people choose convenience over security due to that friction. 

For instance, if the same password is used for more than one system, then the password being compromised on one system could make the user vulnerable on other systems. 

Password managers, vaults, and multi-factor authentication (MFA) can be useful tools, but much of the time we’re just adding another layer of passwords to remember. Sending authentication codes via SMS or email has been used as an element of MFA processes, but it relies on “what you have” — a device, an email address, or a phone number — and is subject to spoofing or redirection attacks. 

What if we could be even more specific about authentication? Not just what you have, but who you are?

Unique attributes of each person include various aspects of their biometrics, such as their fingerprints or face. While unique, these identifiers might not be available in environments where the user must wear a mask or gloves. Not all devices have biometric support. 

Another way to ensure a person is who they claim to be is by making use of their behavioral biometrics and environmental factors. This type of authentication is passive, or implicit, meaning a user can be authenticated without disruption. It’s also continuous. As a user moves around, the application is able to detect from one moment to the next whether the person carrying the device is the intended user. 

UnifyID offers a novel method of uniquely authenticating using the way you walk. With GaitAuth, your application can provide authentication just by the user carrying a phone as they walk around. In this article, we will show how easy it is to integrate GaitAuth into an Android application.

GaitAuth in an Android App

The GaitAuth SDK lets you easily incorporate machine learning-powered gait authentication into your applications. 

For this article, I created an example of a vault application that stores secrets intended to be accessed by only one user. Since we want to focus on the ease of integrating GaitAuth, the sample just stores simple text secrets like passwords, key strings, or other confidential data. But it could be easily extended to provide one-time passwords (OTP) using a Time-based One-time Password (TOTP) or HMAC-based One-time Password  (HOTP) algorithm. 

The application presents someone with a password challenge, and once the app authenticates the user from either gait data or a correct password, it displays the secrets.

Before integrating GaithAuth into an application, you will need to sign up for a free developer account. Once you have created an account, you will have access to the Developer Dashboard. 

On the Dashboard, next to SDK Keys, click Create. You’ll be asked for a name for identifying the key. Enter the name of your application or some other meaningful label here. After a name is entered and saved, the SDK key will display on the dashboard. You’ll need this key in your mobile app for SDK initialization. For the sample code accompanying this article, place your key in the Android resource file values/secrets.xml into the item named sdkKey.

To add the SDK to the application, a few lines must be added to the project and application build.gradle file. For the project build.gradle, mavenCentral must be added to the buildscript and allprojects sections.

buildscript {
   repositories {
       google()
       jcenter()
      mavenCentral() // add this line
   }
}

allprojects {
   repositories {
       google()
       jcenter()
      mavenCentral() // add this line
   }
}

In the build.gradle for the module, a line must be added to the dependencies section.

dependencies {
   implementation fileTree(dir: "libs", include: ["*.jar"])
   implementation 'androidx.appcompat:appcompat:1.2.0'
   implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
   implementation 'id.unify.sdk:sdk-gaitauth:1.3.13' // add this line
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test.ext:junit:1.1.2'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

With these changes, a reference to the UnifyID GaitAuth SDK has been added. 

Some permissions are needed that the application might not already have. Within the application’s AndroidManifest.xml file, add the following permissions. 

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

GaitAuth learns how to recognize a user by collecting features from how the person walks. The user should carry the device around for a week so that your application can get to know how the person walks in various situations that may be part of their weekly routine. 

After collecting this feature data, your application will use it to train an ML model for recognizing the user’s steps. A trained model provides a confidence score for the current user being the intended user or for being an imposter. 

Setting Up Feature Collection

The first modification that we want to make to the application is for it to collect features as the user is walking. For collecting features, we place code within an Android service. The service can continue to run and collect features even if the user has navigated to a different application on the device. 

When the service starts, it will initialize the GaitAuth SDK, show a notification to let the user know that it is running, and begin collecting features. 

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if(!isInitialized) {
        super.onStartCommand(intent, flags, startId);
        // The username is passed from the activity that starts the service
        userID = intent.getStringExtra(INTENT_USER_ID);
        initGait();
        initNotification();
        isInitialized = true;
    }
    return START_NOT_STICKY;
}

void initGait() {
   UnifyID.initialize(getApplicationContext(), GetSDKKey(), userID, new CompletionHandler() {
    @Override
    public void onCompletion(UnifyIDConfig config) {
        GaitAuth.initialize(getApplicationContext(), config);
        initModel();
    }
    @Override
    public void onFailure(UnifyIDException e) {
        e.printStackTrace();
        postUpdate(e.getMessage());
        Log.e(TAG, e.getMessage());
    }
    });
}

The gait model object is used for recognizing a user by the features of their steps. When a gait model is created, it receives a unique ID string. The ID string should be saved. The ID string can be used to reload the model when the application restarts later. 

void initModel() {
   // If we have not already instantiated a GaitModel, then create one.
    if(gaitModel == null ) {
        // See if there is a GaitModel ID that we can load.
        SharedPreferences pref = getSharedPreferences(
            PREFERENCE_GAITMODEL, MODE_PRIVATE);
        String modelID = pref.getString(PREFERENCE_KEY_MODELID,"");
        GaitAuth gaitAuth = GaitAuth.getInstance();
        try {
            // If there is no modelID, then create a model and save its ID
            if (modelID == "") {
                gaitModel = gaitAuth.createModel();
                SharedPreferences.Editor editor = pref.edit();
                editor.putString(PREFERENCE_KEY_MODELID, gaitModel.getId());
                editor.commit();
            } else {
                // If there is a modelID, then use it to load the model
                gaitModel = gaitAuth.loadModel(modelID);
            }
        } catch (GaitModelException exc) {
            exc.printStackTrace();
       }
    }
}

Training the Model

Since the model hasn’t been trained, it is not able to recognize the user yet. Let’s collect some features to train the model. As new features are generated, they are added to a collection. When the collection reaches a prescribed size, it is saved to device storage. 

final int FEATURES_TO_HOLD = 250;
Vector<GaitFeature> gaitFeatureList = new Vector<GaitFeature>();

void startFeatureCollection() {
    try {
        GaitAuth.getInstance().registerListener(new FeatureEventListener() {
            @Override
            public void onNewFeature(GaitFeature feature) {
                gaitFeatureList.add(feature);
                if(gaitFeatureList.size()>=FEATURES_TO_HOLD) {
                    saveFeatures();
                }
            }
        });
    } catch (GaitAuthException e) {
        e.printStackTrace();
    }
}

The SDK provides methods for serializing and deserializing feature lists to byte data. We will use the static method GaitAuth.serializeFeatures to make a byte stream from the feature list. This byte stream is then written to storage. 

void saveFeatures()  {
    if (gaitFeatureList.size() == 0) {
        return;
    }
    Vector<GaitFeature> featuresToSave = new Vector<GaitFeature>();
    synchronized (gaitFeatureList) {
       featuresToSave.addAll(gaitFeatureList);
       gaitFeatureList.clear();
    }
    try {
        byte[] featureData = GaitAuth.serializeFeatures(featuresToSave);
        File storageFile = getStorageFile(getNextFileSegment());
        FileOutputStream fos = new FileOutputStream(storageFile);
        fos.write(featureData, 0, featureData.length);
        fos.close();
        addFeatureCount(featuresToSave.size());
        notificationBuilder.setContentText(String.format("Saved feature set %d containing %d elements at %s", getFeatureCount(), featuresToSave.size(), new Date()));
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.notify(1, notificationBuilder.build());
    } catch (FeatureCollectionException | FileNotFoundException exc) {
        exc.printStackTrace();
    } catch (IOException exc) {
        exc.printStackTrace();
    }
}

Once there are enough features, we can initiate training. UnifyID recommends a minimum of three days and 7,000 walk cycles, but suggests using seven days and 10,000 walk cycles for optimal training.

In the sample program, a button on the settings screen starts the training. 

To train the model, we deserialize the features that have been collected and input them into our model using the add method. After the features are added, we call GaitModel.train. Training can take a few hours. GaitModel.getStatus gets the status of the model. The function returns one of the following values:

  • CREATED – the model hasn’t yet been trained.
  • TRAINING – training is in process. Check again later for the training result.
  • READY – the model is trained and ready to begin recognizing users
  • FAILED – the model could not be trained. 

If training fails, GaitModel.getReason returns a string that explains why the failure occurred. Attempting to train the model with a tiny data set results in a message stating that the amount of training data is insufficient. When the function returns READY, we can begin using it to authenticate the user. 

Authenticating the User

For a model that is ready, there are two methods of evaluating this data to determine if a device is in the hands of the intended person. 

One method is to examine the gait feature score. Possible scores range from -1.0 to 1.0. 

Positive scores indicate the current user is the person whose walking patterns were used to train the model. 

Negative scores indicate the person holding the device is an imposter. 

Your organization may want to evaluate different thresholds for acceptance to find one that is acceptable. A passing score of 0.6 to 0.8 is a good starting point. 

In the following code, the scores of the last four features collected are averaged together. If the most recent feature was collected within the past 60 seconds and the average is greater than 0.6, then the user is considered authenticated.

static final float PASSING_SCORE = 0.6f;
static final long  MAX_PASSING_AGE = 30000;
static final int MIN_SCORE_COUNT = 4;

public boolean isAuthenticated() {
    if(gaitScoreList.size()>=MIN_SCORE_COUNT) {
        long age = (new Date()).getTime() - gaitFeatureList.get(gaitFeatureList.size()-1).getEndTimeStamp().getTime();
        float sum = 0.0f;
        for(GaitScore score:gaitScoreList) {
            sum += score.getScore();
        }
        float avg = sum / (float)gaitScoreList.size();
        if(avg > PASSING_SCORE && age <=MAX_PASSING_AGE) {
            return true;
        }
    }
    return false;
}

When examining the feature scores to authenticate the user, there is freedom for deciding if a user will be authenticated or not for certain scenarios. 

There is also a much easier way to authenticate a user. The GaitAuth SDK provides an authenticator that can collect features and perform authentication for you. 

To create a GaitAuth authenticator, first create a configuration. The configuration contains settings for the maximum age of features to consider, setting a threshold for a passing feature, and other attributes that affect how the features are evaluated.

GaitQuantileConfig config = new GaitQuantileConfig(QUANTILE_THRESHOLD);
config.setMinNumScores(1);    // Require at least 1 score
config.setMaxNumScores(50);   // Set the maximum number of scores to use for authentication
config.setMaxScoreAge(10000); // Set the maximum age, in milliseconds, for features
config.setNumQuantiles(100);  // Set the number of quantiles (divisions) for the feature data
config.setQuantile(50);

Once created, the authenticator continues to collect information from the user’s walking. The authentication status can be checked at any time by calling getStatus() on the authenticator. The call to getStatus() accepts an  AuthenticationListener. Either the onCompletion or onFailure method on this object will be called. 

Note that if onCompletion is called, that does not imply that the user was authenticated. A call to onCompletion means that the authenticator was able to perform an evaluation. To determine if the user is authentic, check isAuthenticated.

gaitAuthenticator.getStatus(new AuthenticationListener() {
   @Override
   public void onComplete(AuthenticationResult result) {
       gaitAuthenticatorResult = result.getStatus();
              
       switch(result.getStatus())
       {
           case AUTHENTICATED:
               isAuthenticated = true;
           break;
           case UNAUTHENTICATED:
               isAuthenticated = false;
           Default:
	     break;
       }
   }
   @Override
   public void onFailure(GaitAuthException cause) {

   }
});

The authenticator will continue to authenticate the user in the background. 

The application unlocks the stored secrets when it detects the user recently walked and that it was an authorized user. If the application cannot authenticate the user from their walk (the user hasn’t recently walked), the password unlock feature is available as a fallback.

Next Steps

We now have a password vault application that is able to recognize a user by the way that they walk. We did this by adding the GaitAuth SDK to the project, collecting features based on the user’s gait, and using the features to train a model to recognize the user. The application unlocks for the user when the trained model recognizes their gait. As noted earlier, we can easily change the application functionality to add features like one-time passwords.

You can read more about the GaitAuth SDK at UnifyID’s documentation site. Sign up for free on the developer dashboard to begin testing with the SDK. For a more in-depth look at how gait biometric verification works, see this publication on the UnifyID site. You can download the sample application used in this blog post from https://github.com/UnifyID/blog-android-secrets.