The general purpose of this service is establish a communication channel between two devices using Bluetooth specifically implementing the GATT protocol , additionally, the library exposes multiple tools to facilitate the encrypt and decrypt, currently just is implemented the AES protocol using CBC, CTR, ECB, CFB and OFB modes of operation.
- Installation
- AES cypher
- Bluetooth BLE
to install @nebulae/angular-ble
library in your angular project just execute the command
npm install @nebulae/angular-ble
- Be sure you already have install
@types/web-bluetooth
andaes-js
, if already not installed, execute the next commands
npm install @types/web-bluetooth
npm install aes-js
Import the AngularBleModule
in your module
import { NgModule } from '@angular/core';
import { AngularBleModule } from 'angular-ble';
import { AppComponent } from './app.component';
@NgModule({
imports: [
//...,
AngularBleModule.forRoot()
]
//...,
})
export class AppModule {}
-
Use AES cypher in your service/component
here is an annotated example using the
@nebulae/angular-ble
cypher serviceimport { Component, OnInit } from '@angular/core'; import { CypherAesService } from 'angular-ble'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { // input value used to encrypt valueToEncrypt = ''; // encrypted value converted to hex encryptedHex = ''; // encrypted value converted to bytes encryptedBytes = ''; // input value used to decrypt valueToDecrypt = ''; // decrypted value converted to hex decryptedHex = ''; // decrypted value converted to bytes decryptedBytes = ''; // decrypted value converted to text decryptedText = ''; constructor(private cypherAesService: CypherAesService) {} title = 'angular-ble-app'; ngOnInit(): void { } encryptValue() { // Call this method to configurate the aes service this.cypherAesService.config([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]); /* Next to it call the service and specifically the method called encrypt, this returns an Uint8Array than contains the encrypted input value. The encrypt method only receives Uint8Array or Uint16Array or Uint32Array, because of this the text is parsed to Uint8Array */ const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(this.valueToEncrypt)); this.encryptedHex = this.cypherAesService.bytesTohex(encryptedValue); this.encryptedBytes = '[' + Array.from(encryptedValue).toString() + ']'; } decryptValue() { // Call this method to configurate the aes service this.cypherAesService.config([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ]); /* Next to it call the service and specifically the method called decrypt, this returns an Uint8Array than contains the decrypted input value. To this example the input receives an encrypted hexa. but the decrypt method only receives Uint8Array or Uint16Array or Uint32Array, because of this the hexa is parsed to Uint8Array */ const decryptedValue = this.cypherAesService.decrypt(this.cypherAesService.hexToBytes(this.valueToDecrypt)); this.decryptedHex = this.cypherAesService.bytesTohex(decryptedValue); this.decryptedBytes = '[' + Array.from(decryptedValue).toString() + ']'; this.decryptedText = this.cypherAesService.bytesToText(decryptedValue); } }
-
Use Bluetooth BLE in your service/component
here is an annotated example using the
@nebulae/angular-ble
bluetooth service
import { Component, OnInit } from '@angular/core';
import { CypherAesService } from 'angular-ble';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
// Current device status
deviceConnected = false;
// Current device battery level
batteryLevel = '';
// Current device manufacturer name
manufacturerName = '';
// Current device model number
modelNumber = '';
// Current device serial number
serialNumber = '';
// Current device hardware Revision
hardwareRevision = '';
// Current device firmware revision
firmwareRevision = '';
// Current device software revision
softwareRevision = '';
// Current device system id
systemId;
// Current device pnp id
pnpId;
constructor(private bluetoothService: BluetoothService) {}
title = 'angular-ble-app';
ngOnInit(): void {
// Start a listener that delivers the currently connected device (if the returned
// device is null means than the device connection has been lost)
this.bluetoothService.getDevice$().subscribe(device => {
this.deviceConnected = device? true : false;
});
}
//stablish a connection between the browser and a bluetooth device
connectToDevice() {
this.bluetoothService.connectDevice$().subscribe(res => {});
}
// end the stablished connection between the browser and a bluetooth
//device
disconnectToDevice() {
this.bluetoothService.disconnectDevice();
}
//get the current device battery level
getBatteryLevel() {
this.bluetoothService.getBatteryLevel$().subscribe(res => {
this.batteryLevel = res+"";
})
}
}
This service use as base the library aes-js
All keys must be 128 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 bytes) long.
The library work with Array, Uint8Array and Buffer objects as well as any array-like object (i.e. must have a length property, and have a valid byte value for each entry).
// 128-bit, 192-bit and 256-bit keys
const key_128 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
const key_192 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23];
const key_256 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31];
// or, you may use Uint8Array:
const key_128_array = new Uint8Array(key_128);
const key_192_array = new Uint8Array(key_192);
const key_256_array = new Uint8Array(key_256);
There are several modes of operations, each with various pros and cons. In general though, the CBC and CTR modes are recommended. The ECB is NOT recommended., and is included primarily for completeness.
const textToEncrypt = 'Text may be any length you wish, no padding is required.';
// An example 128-bit key (16 bytes * 8 bits/byte = 128 bits), CTR doesnt require inital vector so this param is passed as undefined and as additional param is added the value counter (required)
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], undefined, 'CTR', {counter: 5});
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));
console.log(encryptedValue);
//The result must be: [143, 148, 252, 101, 14, 212, 112, 111, 121,
// 19, 78, 143, 174, 156, 210, 4, 246, 31, 104, 126, 222, 170, 190, 21, 112, 94, 124, 48
// , 58, 166, 224, 223, 52, 66, 62, 233, 223, 77, 149, 189, 71, 231, 80, 228, 146, 178,
// 173, 8, 215, 99, 175, 0, 35, 20, 27, 187]
// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([143, 148, 252, 101, 14, 212, 112, 111, 121,
19, 78, 143, 174, 156, 210, 4, 246, 31, 104, 126, 222, 170, 190, 21, 112, 94, 124, 48
, 58, 166, 224, 223, 52, 66, 62, 233, 223, 77, 149, 189, 71, 231, 80, 228, 146, 178,
173, 8, 215, 99, 175, 0, 35, 20, 27, 187]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: Text may be any length you wish, no padding is required.
// If the text is not a 16 byte size, the library use a padding method to fill the missing with 0xFF
const textToEncrypt = 'TextMustBe16Byte';
// An example 128-bit key
// The initialization vector must be 16 bytes (this value is optional, the predefined value is a 16 bytes iv empty 0xFF)
// The CBC method is the default so doesn't is necessary set the method type
this.cypherAesService.config([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ],
[ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ]);
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));
console.log(encryptedValue);
//The result must be: [16, 79, 176, 115, 249, 161, 49, 242, 202, 180, 145, 132, 187, 134, 76, 162]
// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([16, 79, 176, 115, 249, 161, 49, 242, 202, 180, 145, 132, 187, 134, 76, 162]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: TextMustBe16Byte.
// the text must be multiple of the segment size assigned
const textToEncrypt = 'TextMustBeAMultipleOfSegmentSize';
// An example 128-bit key
// The initialization vector must be 16 bytes (this value is optional, the predefined value is a 16 bytes iv empty 0xFF)
// As additional param is added the value segmentSize (required)
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36], 'CFB', { segmentSize: 8 });
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));
console.log(encryptedValue);
//The result must be: [85, 227, 175, 38, 56, 197, 96, 180, 253, 185, 210, 106, 99, 7, 51, 234, 96, 25, 126, 194,
// 61, 235, 133, 177, 246, 15, 113, 241, 4, 9, 206, 39]
// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([85, 227, 175, 38, 56, 197, 96, 180, 253, 185, 210, 106, 99, 7, 51, 234, 96, 25, 126, 194, 61, 235, 133, 177, 246, 15, 113, 241, 4, 9, 206, 39]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: TextMustBeAMultipleOfSegmentSize.
const textToEncrypt = 'Text may be any length you wish, no padding is required.';
// An example 128-bit key
// The initialization vector must be 16 bytes (this value is optional, the predefined value is a 16 bytes iv empty 0xFF)
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36], 'OFB');
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));
console.log(encryptedValue);
//The result must be: [85, 227, 175, 38, 85, 221, 114, 185, 243, 36, 86, 4, 47, 57, 186, 233,
// 172, 207, 246, 37, 145, 89, 230, 8, 190, 85, 161, 170, 49, 60, 89, 141, 180,
// 177, 132, 6, 216, 156, 131, 132, 28, 157, 26, 241, 59, 86, 222, 142, 218, 143, 207, 233, 236, 142, 117, 232]
// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([85, 227, 175, 38, 85, 221, 114, 185, 243, 36, 86, 4, 47, 57, 186, 233,
172, 207, 246, 37, 145, 89, 230, 8, 190, 85, 161, 170, 49, 60, 89, 141, 180,
177, 132, 6, 216, 156, 131, 132, 28, 157, 26, 241, 59, 86, 222, 142, 218, 143, 207, 233, 236, 142, 117, 232]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: Text may be any length you wish, no padding is required..
// If the text is not a 16 byte size, the library use a padding method to fill the missing with 0xFF
const textToEncrypt = 'TextMustBe16Byte';
// An example 128-bit key
// ECB doesnt require inital vector so this param is passed as undefined
this.cypherAesService.config([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
undefined, 'ECB');
const encryptedValue = this.cypherAesService.encrypt(this.cypherAesService.textToBytes(textToEncrypt));
console.log(encryptedValue);
//The result must be: [167, 217, 59, 53, 54,
// 133, 25, 250, 195, 71, 73, 141, 236, 24, 180, 88]
// When ready to decrypt the data, use the decrypt method
const decryptedValue = this.cypherAesService.decrypt(new Uint8Array([167, 217, 59, 53, 54,
133, 25, 250, 195, 71, 73, 141, 236, 24, 180, 88]));
console.log(this.cypherAesService.bytesToText(decryptedValue));
//The result must be: Text may be any length you wish, no padding is required..
This method is used to configurate the params required by the cypher service
// key used to encrypt and decrypt (Required)
const masterKey = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
// vector used to encrypt abd decrypt except when ECB encrypt method is used (optional)
const initialVector = [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36];
// type of encrypt method is used, the possible options are: CBC, CTR, CFB, OFB, ECB (optional)
const encryptMethod = 'CFB';
//configuration params used by the selected encrypt method.
// Note: if the method CTR or CFB is used this param is required otherwise is an optinal param.
// By CTR require the param counter and by CFB require the param segmentSize
const additionalEncryptMethodParams = { segmentSize: 8 };
// defines if the initial vector is changed or not when the data are encrypted or not
const isStaticInitialVector = true;
this.cypherAesService.config(masterKey,
initialVector, encryptMethod, additionalEncryptMethodParams, isStaticInitialVector);
Encrypt the data using the encrypt method previously configured. The data must be a Uint8Array or Uint16Array or Uint32Array
this.cypherAesService.encrypt(dataToEncrypt)
Decrypt the data using the encrypt method previously configured. The data must be a Uint8Array or Uint16Array or Uint32Array
this.cypherAesService.decrypt(dataToDecrypt)
Change the current initalVector
this.cypherAesService.changeInitialVector(newInitalVector)
Change the current encyptMethod
this.cypherAesService.changeEncryptMethod(newEncryptMethod)
Change the current isStaticInitialVector
this.cypherAesService.changeStaticInitialVector(true|false)
Change the current masterKey
this.cypherAesService.changeMasterKey(newMasterKey)
Convert the text to bytes
this.cypherAesService.textToBytes(text)
Convert the bytes to text
this.cypherAesService.bytesToText(bytes)
Convert the bytes to hex
this.cypherAesService.bytesTohex(bytes)
Convert the hex to bytes
this.cypherAesService.hexToBytes(hex)
this service is based on Web Bluetooth please see for more info
get the current device, if the device return null is because the connection has lost
this.bluetoothService.getDevice$().subscribe(device => {
//here you receive the device
});
start a stream by notifiers characteristics
this.bluetoothService.startNotifierListener$('battery_service','battery_level').subscribe(result => {
console.log('stream value: ', result);
});
Discover all available devices and connect to a selected device
this.bluetoothService.connectDevice$().subscribe(res => {
//here you receive the device if the connection is succeful
});
Disconnect the current device this.bluetoothService.disconnectDevice();
get a data from the device using a service and the characteristic
this.bluetoothService.readDeviceValue$('battery_service','battery_level').subscribe(result => {
console.log('stream value: ', result);
});
Send a message using a notifier characteristic (message must be in bytes)
this.bluetoothService.sentToNotifier$('hereMessage','here service', 'here characterisitic').subscribe(result =>{})
Get a primary service instance using the service UIID or GATT identifier
this.bluetoothService.getPrimariService$('here_the_service_identifier').subscribe(result =>{
console.log('Service instance: ',result);
})
Get a characterisitic instance using the service instance and a characteristic UUID
this.bluetoothService.getCharacteristic$(serviceInstance,'here_the_characteristic_identifier').subscribe(result =>{
console.log('Service instance: ',result);
})
The Battery Level characteristic is read using the GATT Read Characteristic Value sub-procedure and returns the current battery level as a percentage from 0% to 100%; 0% represents a battery that is fully discharged, 100% represents a battery that is fully charged
this.bluetoothService.getBatteryLevel$().subscribe(res => {
this.batteryLevel = res+"";
})
This characteristic represents the name of the manufacturer of the device.
this.bluetoothService.getManufacturerName$().subscribe(res => {
this.manufacturerName = res;
});
This characteristic represents the model number that is assigned by the device vendor.
this.bluetoothService.getModelNumber$().subscribe(res => {
this.modelNumber = res;
});
This characteristic represents the serial number for a particular instance of the device.
getSerialNumber() {
this.bluetoothService.getSerialNumber$().subscribe(res => {
this.serialNumber = res;
});
}
This characteristic represents the hardware revision for the hardware within the device.
getHardwareRevision() {
this.bluetoothService.getHardwareRevision$().subscribe(res => {
this.hardwareRevision = res;
});
}
This characteristic represents the firmware revision for the firmware within the device.
getFirmwareRevision() {
this.bluetoothService.getFirmwareRevision$().subscribe(res => {
this.firmwareRevision = res;
});
}
This characteristic represents the software revision for the software within the device.
getSoftwareRevision() {
this.bluetoothService.getSoftwareRevision$().subscribe(res => {
this.softwareRevision = res;
});
}
This characteristic represents a structure containing an Organizationally Unique Identifier (OUI) followed by a manufacturer-defined identifier and is unique for each individual instance of the product.
getSystemId() {
this.bluetoothService.getSystemId$().subscribe(res => {
this.systemId = res+"";
});
}
The PnP_ID characteristic is a set of values used to create a device ID value that is unique for this device.
getPnpId() {
this.bluetoothService.getPnpId$().subscribe(res => {
this.pnpId = res+"";
});
}