Introduction
📱👋 Greetings Swift Developer Community! In the ongoing Swift for Android series of articles, We will continue building apps in the Android Studio that use Swift for the Android platform. It is now possible to compile and write the Swift code in Android Studio with SCADE’s Swift toolchain. With SCADE’s Swift toolchain for Android Studio, you can now easily use Swift to develop Android apps. You can easily add Swift packages to your Android Studio projects using the Swift Package Manager (SPM). To make this happen, SCADE has an SPM library “swift-java” that significantly simplifies communication with JVM from Swift. Without using this library, one needs to write a large of interoperability methods using the SilegenName attribute.
In this article, we delve into a compelling case where an Android app not only seamlessly integrates Swift through the Swift Package Manager (SPM) but also leverages Swift-Crypto for advanced security features. We will build a simple Android app to accept the password string and confirm-password string and we will return whether the password matches using the Swift-Crypto library.
Prerequisite
If you haven’t installed the SCADE IDE, download the SCADE IDE and install it on your macOS system. The only prerequisite for SCADE is the Swift language (at least basics). Also, please ensure that Android Studio with an Android emulator or physical device is running to test the Android application.
To know more about how to add Swift to an existing Android project, please go through these articles:
- Display list of GitHub followers using Github API & Swift PM library - https://medium.com/@SCADE/implement-recyclerview-using-swift-pm-libraries-eacc1efd48af
- Using Swift PM libraries in the Android Studio projects - https://medium.com/@SCADE/using-swift-pm-libraries-in-the-android-studio-projects-7cef47c300bf
Source Code: https://github.com/scade-platform/FusionExamples/tree/main/SwiftCryptoAndroidExample
Design the UI
For our secure password validator app, we'll need a user-friendly interface for users to interact with. Here are some UI elements to be created in Activity’s XML:
- Text Input Fields: We'll include text fields for users to input their desired password and confirm it.
- Validation Result Text View: A TextView to display the validation result, indicating whether the passwords match or not.
- Validation Button: A button to trigger the validation of Swift-Crypto’s method.
- Material Design Elements: We'll utilize Android’s Material Design components for a clean and modern look.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/topLayout"
android:layout_margin="4dp"
android:gravity="center"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="32dp"
android:hint="Enter the password">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordET"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebPassword"
android:textColor="@color/black"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/confirmPasswordLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="32dp"
android:hint="Confirm password">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/confirmPasswordET"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textWebPassword"
android:textColor="@color/black"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/validatePasswordBtn"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="40dp"
android:gravity="center"
android:padding="4dp"
app:backgroundTint="@color/colorPrimary"
android:text="Validate Password"
android:textColor="@color/white" />
<TextView
android:id="@+id/validationResultTV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginTop="22dp"
android:gravity="center"
android:padding="8dp"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="italic" />
</LinearLayout>
The layout will be simple yet effective, allowing users to easily enter their passwords and receive immediate feedback on their validity.
Create Swift Methods in Activity
In order to make the Android app work smoothly and connect the Java part with Swift, we need to set up SwiftFoundation. This setup is crucial as it lets the app run Swift methods and share their results with the Java part of the app through a bridge called JNI. We kickstart this process in the onCreate()
method of the app's activity. This way, we establish the connection between Java and Swift, ensuring the app can take advantage of Swift's capabilities and work seamlessly. This connection enhances the app's overall performance and flexibility.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
// initializing swift runtime.
// The first argument is a pointer to java context (activity in this case).
// The second argument should always be false.
org.swift.swiftfoundation.SwiftFoundation.Initialize(this, false);
} catch (Exception err) {
android.util.Log.e("SwiftAndroidExample", "Can't initialize swift foundation: " + err.toString());
}
// loading dynamic library containing swift code
System.loadLibrary("SwiftAndroidExample");
}
Add Swift Crypto library using SPM
In the Package.swift
file, we're setting up the basic rules for our Swift package. We're specifying that we need at least Swift version 5.8 to run our code. We're also adding two important tools, "swift-java" and "swift-crypto," which help us connect Swift and Java and perform secure cryptographic operations.
dependencies: [
.package(url: "https://github.com/scade-platform/swift-java.git", branch: "main"),
.package(url: "https://github.com/apple/swift-crypto.git", .branch("main"))
]
With these pieces in place, we're ready to create a dynamic library that makes it easy to use Swift and Java in our Android app.
dependencies: [
.product(name: "Java", package: "swift-java"),
.product(name: "Crypto", package: "swift-crypto")
])
Implement Crypto methods in Swifts
Import the modules
We will first import the necessary required modules for implementing Swift-Crypto methods to hash the password. The Crypto package is the most important as it contains the SHA256 class to hash the password. The Java package is basically the compiler of Java objects to Swift equivalent.
import Foundation
import Dispatch
import Java
import Crypto
Implement hashing of password
The hashPassword()
function securely hashes a password using Swift Crypto. First, it turns the password into a special type of data object. Then, it combines this password data with a unique piece of information called a "salt." It takes this combined data and applies a mathematical process (SHA-256) to create a unique hash. Finally, it converts this hash into a readable format and returns it. The salt adds an extra layer of security, making it harder for attackers to guess the original password.
// Function to hash a password securely using Swift Crypto
public func hashPassword(_ password: String, salt: Data) throws -> String {
let passwordData = Data(password.utf8)
// Concatenate the salt and password
let saltedPassword = salt + passwordData
// Hash the salted password using SHA-256
let hashedPassword = SHA256.hash(data: saltedPassword)
// Encode the hashed password as base64 for storage
return Data(hashedPassword).base64EncodedString()
}
Create Salt Key
The function generateRandomSalt()
creates a random and unique "salt" – an api key or equivalent. It generates 16 random numbers (bytes) and forms them into a unique data structure. The randomness comes from a secure source called the SystemRandomNumberGenerator
. This salt is crucial for making it harder for attackers to crack passwords because it's different for each user, even if they have the same password. It adds an extra layer of protection by ensuring that the hashed password will be unique for each user.
// Function to generate a random salt
public func generateRandomSalt() -> Data {
var randomBytes = [UInt8](repeating: 0, count: 16)
var generator = SystemRandomNumberGenerator()
randomBytes.withUnsafeMutableBufferPointer { buffer in
for i in buffer.indices {
buffer[i] = generator.next()
}
}
return Data(randomBytes)
}
Implement Verify Password Method
The verifyPassword()
function checks if a password matches a previously hashed value securely. It starts by decoding the stored hashed password from its base64 format. Then, it prepares the provided password and combines it with the original salt. It also applies the same SHA-256 process used during hashing to create a new hash for the provided password.
Finally, it compares this new hash to the stored one. If they match, the function returns 'true,' indicating that the password is correct; if not, it returns 'false,' indicating the password is incorrect.
// Function to verify a password against a hashed value
public func verifyPassword(_ password: String, against hashedPassword: String, salt: Data) throws
-> Bool
{
// Decode the stored hashed password from base64
guard let storedHashedPasswordData = Data(base64Encoded: hashedPassword) else {
return false
}
let passwordData = Data(password.utf8)
// Concatenate the salt and provided password
let saltedPassword = salt + passwordData
// Hash the salted password using SHA-256
let computedHashedPassword = SHA256.hash(data: saltedPassword)
// Verify if the computed hash matches the stored hash
return computedHashedPassword == storedHashedPasswordData
}
Call Swift Methods from Android’s Activity
The method validatePassword()
is declared in MainActivity and implemented later in the Swift file. It is required to carry out the specialized password validation or security-related functions in the Swift section of the Android application. This arrangement facilitates the seamless cooperation of Java and Swift, enabling Java to trigger Swift code for specific tasks and functions.
private native void validatePassword(String passwordString,
String confirmPasswordString);
As the next step, we will call the validatePassword()
function on the click of Validate Password button and pass the password string and confirm-password string. It will internally call the Swift implementation of the method validatePassword
.
validateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String passwordString = passwordET.getText().toString().trim();
String confirmPasswordString = confirmPasswordET.getText().toString().trim();
if (TextUtils.isEmpty(passwordString) || TextUtils.isEmpty(confirmPasswordString)) {
Toast.makeText(getApplicationContext(), "One or more fields are empty!", Toast.LENGTH_SHORT).show();
return;
}
// starting validation of password
validatePassword(passwordString, confirmPasswordString);
}
});
Implementation of method in Swift
In the Swift implementation of validatePassword()
method, an important linkage between Java and Swift is established. Using the @_silgen_name
attribute, the Swift function "MainActivity_validatePassword" is given a specific native name that Java can recognize when calling this function. It takes several parameters, including a reference to the Java environment ("env"), the Java activity object ("activity"), and two Java strings, "passwordString" and "confirmPasswordString."
It uses the Swift representation of the activity object using JObject
. It then converts the Java strings into Swift strings, making the data compatible for processing within Swift.
Finally, it initiates the password validation process by calling the validatePassword()
swift function, utilizing the converted data.
// NOTE: Use @_silgen_name attribute to set native name for a function called from Java
@_silgen_name("Java_com_example_swiftandroidexample_MainActivity_validatePassword")
public func MainActivity_validatePassword(
env: UnsafeMutablePointer<JNIEnv>, activity: JavaObject, passwordString: JavaString,
confirmPasswordString: JavaString
) {
// Create JObject wrapper for activity object
let mainActivity = JObject(activity)
// Convert the Java string to a Swift string
let passwordStr = String.fromJavaObject(passwordString)
let confirmPasswordStr = String.fromJavaObject(confirmPasswordString)
// Start the password validation
validatePassword(activity: mainActivity, passwordStr: passwordStr, confirmPasswordStr: confirmPasswordStr)
}
The Swift method validatePassword()
is responsible for both hashing (encrypting) a password and verifying it against the confirm password, subsequently executing a callback within the main activity to relay the validation result. It first generates a random "salt," which adds a layer of security to the password hashing process. The password is then hashed using the hashPassword
swift function, incorporating the password and salt.
The hashed password is compared to the confirm-password string for validation using the verifyPassword()
function, with the same salt used for both. Based on the result of this comparison, the function calls a Java method using the activity
instance. If the passwords match, it calls onPasswordValidated()
java method with the successful message string and if they don't match, it sends an unsuccessful message string.
Implementation of function in Java (onPasswordValidated
)
The Java method onPasswordValidated
is invoked from Swift code, and it takes a parameter called validationResult()
, which is a string. The method sets the text of this resultant TextView to the value passed in the "validationResult" parameter. Essentially, this method is responsible for displaying the validation result in the app's user interface.
// method is called from swift code, and the result is sent as an input parameter
public void onPasswordValidated(String validationResult) {
validationResultTV.setText(validationResult);
}
Run the App!
The development phase is complete, and it's time to put the app to the test. We need to ensure that Swift methods can successfully make API calls and return the data to the Activity. Before proceeding, please make sure that you have an emulator or a physical device up and running.
Working with Swift libraries, such as Swift-Crypto, in Android Studio has been a fascinating experience. For those curious about trying out Swift libraries in this environment, you're in for a treat! It's an exciting opportunity to tap into the power of Swift within your Android Studio projects. Give it a try, and you might just find it as cool and rewarding as we did! 😊🎉
Top comments (0)