wiki:security/electronic_signatures

Version 6 (modified by Sam Habiel, 11 years ago) ( diff )

--

Use of Electronic Signatures to Secure VISTA Data

Statement of Problem

The crux of the issue is the ability to store data on patients and be able to detect if the patient data has been changed intentionally or unintentionally since it was entered. Compounding this issue is the need for different people at different points in time to be able to see the data that was entered. This means that the data creator's token cannot be used as a way to secure the data against future modification. The system has to also support document retraction and reassignment, which override the data creator's authority on the document.

An example is in order: how do you know if the text of a TIU note is the same as what the original user entered?

Implementation in VISTA

Various parts of VISTA accomplish this using the encryption and decryption functionality. The encryption algorithm used is a stream cipher (I am not exactly sure which one). For TIU documents, the cipher algorithm uses the document's checksum of document contents as one of the vectors in the algorithm to encrypt the signer's name and title. If the document is modified outside of TIU, the user will see gibberish for these two fields.

For example, this is a typical signature:

/es/ DOCTOR MCDUCK, MD
ENT PHYSICIAN
Signed: 02/27/2013 09:34

If the document is changed outside of TIU, it may look like this (this example isn't real as I do not have access to the algorithm):

/es/ lks&*(*% *(*&@#$*&
A#@^*(ssDASDF
Signed: 02/27/2013 09:34

Other VISTA packages, notably Radiology, implement the same functionality, using other vectors for encryption.

Use in VISTA outside of the VA

The FOIA process removes sensitive security related algorithms from VISTA. As a result, those using VISTA outside of the Veteran's Administration have had to supplement this. I have surveyed all the external VISTAs and here is what I found on how they were replaced:

ItemWorldVistAOpenVistavxVista
Access/Verify HashReversible HashNoneMD5 Hash
Electronic Signature HashNoneRestore VA RoutineMD5 Hash
Encryption/DecryptionNoneRestore VA RoutineBlowfish symmetric block cipher

As you can see from this table, both OpenVista and WorldVistA have done badly (it's not known whether the OpenVista version is even legal) while vxVista has done the best.

A solution to hashing and encryption on GT.M/Unix

I would have used vxVista's solution; but it only works on Cache (both the hash and encryption). For GT.M on Unix, I used the OS pipes to do the work using the well-respected openssl library. For hashing, I use SHA512 with the user's creation date and DUZ as salt. For encryption, I use AES/Rijndael using 256-bit key in Cipher Block Chaining mode. Corresponding methods can be written for Cache on Unix.

UJOSHSHP ; VEN/SMH - Encrypt Data a la XUSHSHP ;2013-02-27  9:44 AM
 ;;1.0;JORDAN SPECIFIC MODIFICATIONS;
 ;
HASH ; HASH
 Q:'$D(X)  Q:'$L(X)
 ; Input: X from Symbol Table. X can be any length
 ; Output: X hashed, as 20 characters
 ;
 ; Use sha512 with user stuff as salt
 ; Should use PBKDF2, bcrypt, or scrypt; but not available in openssl CLI.
 S X=$$UP^XLFSTR(X) ; Uppercase X; per original VA code.
 N SALTED S SALTED=$P(^VA(200,DUZ,1),U,7)_X_DUZ ; Salt X with User Creation Date and DUZ.
 N CALL S CALL="openssl dgst -sha512" ; SHA512 OS call
 N DEVICE S DEVICE="hashPipe"
 N OUTPUT
 N OLDIO S OLDIO=$IO
 OPEN DEVICE:(shell="/bin/sh":comm=CALL)::"PIPE"
 USE DEVICE WRITE SALTED,/EOF READ OUTPUT:1 U OLDIO CLOSE DEVICE
 S X=$E(OUTPUT,$L(OUTPUT)-19,$L(OUTPUT)) ; X is the return variable; only 20 characters long
 QUIT
 ;
EN ; ENCRYPT
 Q:'$D(X)!('$D(X1))!('$D(X2))  Q:'$L(X)
 ; Expect X, X1, X2
 ; X is the string to encrypt. X1 and X2 are salts/password
 ; X1 can be the DUZ
 ; X2 can be the checksum of the text to encrypt
 ; Output: X, the encrypted string. Length is the same the input.
 ; Maximum supported length is maximum global node length.
 ; Encrypting word processing fields is not supported.
 ;
 ; AES- Cipher Block Chaining with Base 64 encoding with password w no salt.
 N CALL S CALL="openssl enc -a -aes-256-cbc -nosalt -pass pass:"_X1_X2
 ; N CALL S CALL="openssl enc -a -des-ede3-cbc -nosalt -pass pass:"_X1_X2
 ; N CALL S CALL="openssl enc -bf-cbc -nosalt -pass pass:"_X1_X2
 ; N CALL S CALL="openssl enc -a -rc4 -nosalt -pass pass:"_X1_X2
 N DEVICE S DEVICE="encryptionPipe"
 N OLDIO S OLDIO=$IO
 OPEN DEVICE:(shell="/bin/sh":comm=CALL)::"PIPE"
 USE DEVICE
 WRITE X,/EOF ; Write the string to encrypt
 N I,OUTPUT ; I = counter. OUTPUT = encyrpted tems
 FOR I=1:1 READ OUTPUT(I) Q:$ZEOF  ; Read it into Output Array
 U OLDIO
 CLOSE DEVICE
 ; Next two lines: concatentate the array into the root node
 S OUTPUT="" 
 F I=1:1 Q:'$D(OUTPUT(I))  S OUTPUT=OUTPUT_OUTPUT(I) K OUTPUT(I)
 ; X is the output from the API
 S X=OUTPUT
 QUIT
 ;
DE ; DECRYPT
 Q:'$D(X)!('$D(X1))!('$D(X2))  Q:'$L(X)
 ; Expect X, X1, X2
 ; X is the string to encrypt. X1 and X2 are salts/password
 ; X1 can be the DUZ
 ; X2 can be the checksum of the text to encrypt
 ; Output: X, the decrypted string. Length is the same the input.
 ; Maximum supported length is maximum global node length.
 ; Encrypting word processing fields is not supported.
 N CALL S CALL="openssl enc -d -a -aes-256-cbc -nosalt -pass pass:"_X1_X2
 ; N CALL S CALL="openssl enc -a -d -rc4 -nosalt -pass pass:"_X1_X2
 N DEVICE S DEVICE="decryptionPipe"
 N OLDIO S OLDIO=$IO
 OPEN DEVICE:(shell="/bin/sh":comm=CALL)::"PIPE"
 USE DEVICE
 F  W $E(X,1,64),! S X=$E(X,65,1024*1024*1024) Q:X=""  ; Write Input base64 style
 WRITE /EOF
 N OUTPUT R OUTPUT:1 ; Read Output of decryption back.
 U OLDIO
 CLOSE DEVICE
 S X=OUTPUT ; X is output
 QUIT
 ;
RUNTESTS S IO=$P S DIQUIET=1 D DT^DICRW D:$L($T(EN^XTMUNIT)) EN^XTMUNIT($T(+0),1) QUIT
THASH ; @TEST - Test Hashing
 N DUZ
 S DUZ=1 N X S X="HELLO WORLD" D HASH
 N RESULT1 S RESULT1=X
 S DUZ=1 N X S X="HELLO WORLD" D HASH
 N RESULT2 S RESULT2=X
 D CHKEQ^XTMUNIT(RESULT1,RESULT2,"Hash results are not equal")
 ;
 S DUZ=1 N X S X="HELLO WORLD" D HASH
 N RESULT1 S RESULT1=X
 S DUZ=2 N X S X="HELLO WORLD" D HASH
 N RESULT2 S RESULT2=X
 D CHKTF^XTMUNIT(RESULT1'=RESULT2,"Hash results are not supposed to be equal")
 ;
 S DUZ=1
 N X S X="Hello World" D HASH
 N RESULT1 S RESULT1=X
 N X S X="HELLO WORLD" D HASH
 N RESULT2 S RESULT2=X
 D CHKEQ^XTMUNIT(RESULT1,RESULT2,"Hash results of different cases aren't equal")
 QUIT
 ;
THASH2 ; @TEST - Test Hashing using main calling routine XUSHSHP
 N DUZ
 S DUZ=1 N X S X="HELLO WORLD" D HASH^XUSHSHP
 N RESULT1 S RESULT1=X
 S DUZ=1 N X S X="HELLO WORLD" D HASH^XUSHSHP
 N RESULT2 S RESULT2=X
 D CHKEQ^XTMUNIT(RESULT1,RESULT2,"Hash results are not equal")
 ;
 S DUZ=1 N X S X="HELLO WORLD" D HASH^XUSHSHP
 N RESULT1 S RESULT1=X
 S DUZ=2 N X S X="HELLO WORLD" D HASH^XUSHSHP
 N RESULT2 S RESULT2=X
 D CHKTF^XTMUNIT(RESULT1'=RESULT2,"Hash results are not supposed to be equal")
 ;
 S DUZ=1
 N X S X="Hello World" D HASH^XUSHSHP
 N RESULT1 S RESULT1=X
 N X S X="HELLO WORLD" D HASH^XUSHSHP
 N RESULT2 S RESULT2=X
 D CHKEQ^XTMUNIT(RESULT1,RESULT2,"Hash results of different cases aren't equal")
 QUIT
 ;
TENC ; @TEST - Test Encryption
 N VALUE S VALUE="Mary has a little lamb"
 S X=VALUE,X1=1,X2=234234234
 D EN,DE ; encrypt, decrypt X
 D CHKEQ^XTMUNIT(VALUE,X,"Encryption and decryption didn't happen properly")
 ;
 N VALUE2 S VALUE2="Mary has a little lamb"
 S X=VALUE2,X1=88,X2=234234234
 D EN ; encrypt
 N ENCSTR1 S ENCSTR1=X
 S X=VALUE2,X1=1,X2=234234234 ; Different X1
 D EN ; encrypt
 N ENCSTR2 S ENCSTR2=X
 D CHKTF^XTMUNIT(ENCSTR1'=ENCSTR2,"Encrypted strings with different passwords are not supposed to be equal")
 QUIT
 ;
TENC2 ; @TEST - Test Encryption by calling XUSHSHP
 N VALUE S VALUE="Mary has a little lamb"
 S X=VALUE,X1=1,X2=234234234
 D EN^XUSHSHP,DE^XUSHSHP ; encrypt, decrypt X
 D CHKEQ^XTMUNIT(VALUE,X,"Encryption and decryption didn't happen properly")
 ;
 N VALUE2 S VALUE2="Mary has a little lamb"
 S X=VALUE2,X1=88,X2=234234234
 D EN^XUSHSHP ; encrypt
 N ENCSTR1 S ENCSTR1=X
 S X=VALUE2,X1=1,X2=234234234 ; Different X1
 D EN^XUSHSHP ; encrypt
 N ENCSTR2 S ENCSTR2=X
 D CHKTF^XTMUNIT(ENCSTR1'=ENCSTR2,"Encrypted strings with different passwords are not supposed to be equal")
 QUIT
LENGTH ; @TEST - Test Lengths
 N X,X1,X2,I
 S (X1,X2)=982734987234
 W !
 F I=1:1:60 K X S $P(X,"A",I)="A" W $L(X) D EN W ?30,$L(X),!
 QUIT

In addition, with Lloyd Milligan's permission, I used the extensible framework he has written to use to call different algorithms based on configuration. The Public Entry Point, XUSHSHP, references these as follows:

XUSHSHP ;SF/STAFF - HASHING ROUTINE FOR SIG BLOCK IN FILE 200 ;2013-02-26  2:32 PM
        ;;8.0;KERNEL;;Jul 10, 1995;Build 2
        ;
HASH ; PEP: HASH; Fallthrough
        D X^UJOXCALL("HASH/DIGEST")
        Q
EN      ; PEP: Encrypt
        D X^UJOXCALL("ENCRYPT STRING")
        Q
DE      ; PEP: Decrypt
        D X^UJOXCALL("DECRYPT STRING")
        Q

UJOXCALL is:

UJOXCALL        ;VEN/SMH - External APIs call wrapper ;2013-02-26  3:38 PM
        ;;1.0;JORDAN SPECIFIC MODIFICATIONS
        Q
X(APINAME)      ; Private Proc; Call API based on files
        ; Input: APINAME, by Value. External API Name in file 400000001.1
        ; Output: None. Executes API. API determines Input and Output variables
        ;
        N IEN S IEN=$O(^UJO(400000001.1,"B",APINAME,"")) Q:'IEN  ; IEN of API, DINUMMED in imp specific code file (400000001.3)
        I $L($G(^UJO(400000001.3,IEN,1))) X ^(1) QUIT  ; Try Implementation specific first
        I $L($G(^UJO(400000001.1,IEN,2))) X ^(2) QUIT  ; Otherwise, use default one
        ;
        ;
        ;
RUNTESTS I $L($T(EN^XTMUNIT)) S IO=$P,DIQUIET=1 D DT^DICRW,EN^XTMUNIT($T(+0),1) QUIT
TESTAPI ; @TEST - Test calling X(APINAME) with existent and non-existent entries.
        N X,X1,X2
        D X("UNIT TEST ENTRY")
        D CHKEQ^XTMUNIT(X,"HELLO WORLD","API wasn't called")
        ;
        N STR S STR="HELLO WORLD2"
        S X=STR,X1="LKJSDF",X2=23432
        D X("ENCRYPT STRING")
        D CHKTF^XTMUNIT(X'="HELLO WORLD2","Encrypt failed")
        D X("DECRYPT STRING")
        D CHKEQ^XTMUNIT(X,"HELLO WORLD2","Encrypt/Decrypt failed")
        ;
        D X("LKSJDKLFDF") ; Make sure there is no crash
        QUIT

The extensible framework uses the file UJO SUPPORTED API (400000001.1) to decide what code to execute. Each entry in this file defines an API that can be called externally from the Mumps Database. Entries in UJO IMPLEMENTATION-SPECIFIC API (400000001.3) file are DINUMMED to this file and override the default implementation of the API specified in this file if a DINUMMED entry is present in the other file.

The relevant entries in the files are:

Output from what File: UJO SUPPORTED API//   (4 entries)
Select UJO SUPPORTED API NAME: HASH/DIGEST  
Another one: ENCRYPT STRING  
Another one: DECRYPT STRING  
Another one: 
Standard Captioned Output? Yes//   (Yes)
Include COMPUTED fields:  (N/Y/R/B): NO//  - No record number (IEN), no Computed
 Fields

NAME: HASH/DIGEST                       APPLICATION GROUP: KERNEL
  DATE CREATED: FEB 25,2013             RESPONSIBLE PERSON: EHS/SW
SUPPORTED VARIABLE: X                   ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: String to hash
  DEFAULT XECUTE: S X=$$UP^XLFSTR(X)
 REMARKS:   
 Function provides functionality of a password hash so that the original passwor
d cannot be decrypted from the hash.
 See http://crackstation.net/hashing-security.htm


NAME: ENCRYPT STRING                    APPLICATION GROUP: KERNEL
  DATE CREATED: FEB 25,2013             RESPONSIBLE PERSON: EHS/SW
SUPPORTED VARIABLE: X                   ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: String to be encrypted
SUPPORTED VARIABLE: X1                  ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: encryption salt/password
SUPPORTED VARIABLE: X2                  ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: encryption salt/password
  DEFAULT XECUTE: Q
 REMARKS:   
 Encrypts data using a password/salt

 The variables X1 and X2 are values to be decided upon by the programmer
 calling this utility.


NAME: DECRYPT STRING                    APPLICATION GROUP: KERNEL
  DATE CREATED: FEB 25,2013             RESPONSIBLE PERSON: EHS/SW
SUPPORTED VARIABLE: X                   ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: Encrypted string to be decrypted
SUPPORTED VARIABLE: X1                  ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: Encryption salt/password
SUPPORTED VARIABLE: X2                  ALWAYS DEFINED: YES
  BRIEF DESCRIPTION: Encryption salt/password
  DEFAULT XECUTE: Q
 REMARKS:   
 Decrypts encrypted text using X1 and X2.
 If X1 and X2 are different from what was used to encrypt the string,
 the encryption will fail.


Select OPTION: INQUIRE TO FILE ENTRIES  



Output from what File: UJO SUPPORTED API// UJO,IM,S  UJO IMPLEMENTATION-SPECIFIC
 API                                      (3 entries)
Select UJO IMPLEMENTATION-SPECIFIC API NAME: HASH/DIGEST  
Another one: ENC  RYPT STRING  
Another one: DEC  RYPT STRING  
Another one: 
Standard Captioned Output? Yes//   (Yes)
Include COMPUTED fields:  (N/Y/R/B): NO//  - No record number (IEN), no Computed
 Fields

NAME: HASH/DIGEST                       XECUTE: D HASH^UJOSHSHP


NAME: ENCRYPT STRING                    XECUTE: D EN^UJOSHSHP


NAME: DECRYPT STRING                    XECUTE: D DE^UJOSHSHP

How to implement

To implement the changes in hashing and encryption, you need to perform conversions while users are offline from the system. The number of conversions you have to perform depends on the number of packages that use these features. In the world outside of the VA, these most likely will be the TIU and Radiology/Nuclear Medicine packages, results in two conversions in addition to that of hashing the electronic signature.

As hashing is a lossy operation, it can only be done once from the plain text. To hash electronic signatures, you need to perform a loop on the new person file like this:

NP ; New Person File electronic signature conversion from plain text to hashed.
 W !,"Hashing electronic signatures",!
 N DUZ ; Needed for the hashing algorithm
 N I S I=0 F  S I=$O(^VA(200,I)) Q:'I  D  ; for each NP
 . N OLDES S OLDES=$P($G(^VA(200,I,20)),U,4) ; Get old signature
 . Q:'$L(OLDES)  ; quit if it doesn't exist.
 . W I_" "
 . S DUZ=I ; DUZ is a hash salt
 . N X S X=OLDES D HASH^XUSHSHP  ; hash old sig
 . S $P(^VA(200,I,20),U,4)=X ; store result
 W !
 QUIT

PLEASE NOTE THAT IF YOU RUN THIS MORE THAN ONCE, YOU WILL NOT BE ABLE TO RETRIEVE BACK THE USER'S ELECTRONIC SIGNATURES.

To convert TIU notes, perform the following:

TIU ; Encrypt Signatures for TIU documents; from plain text to encrypted.
 W !,"Encrypting Signature Names and Titles in TIU Documents",!
 K ^TMP($J,"UJOFDA") ; FDA Array
 N I S I=0 F  S I=$O(^TIU(8925,I)) Q:'I  D  ; for each document
 . N SB S SB=$G(^(I,15)) ; Get signature block **NAKED**
 . Q:SB=""
 . W I_" "
 . ; We get the following fields:
 . ; - 1503      SIGNATURE BLOCK NAME (FOX), [15;3]
 . ; - 1504      SIGNATURE BLOCK TITLE (FOX), [15;4]
 . ; - 1509      COSIGNATURE BLOCK NAME (FXO), [15;9]
 . ; - 1510      COSIGNATURE BLOCK TITLE (FXO), [15;10]
 . N F1503,F1504,F1509,F1510 ; encrypted pieces now in plain text
 . S F1503=$P(SB,U,3),F1504=$P(SB,U,4),F1509=$P(SB,U,9),F1510=$P(SB,U,10) ; get text values
 . ;
 . ; Store in FDA.
 . S:$L(F1503) ^TMP($J,"UJOFDA",8925,I_",",1503)=F1503
 . S:$L(F1504) ^TMP($J,"UJOFDA",8925,I_",",1504)=F1504
 . S:$L(F1509) ^TMP($J,"UJOFDA",8925,I_",",1509)=F1509
 . S:$L(F1510) ^TMP($J,"UJOFDA",8925,I_",",1510)=F1510
 ;
 D CLEAN^DILF ; clean up variables
 D WAIT^DICD ; Please wait a while
 D FILE^DIE("ET",$NA(^TMP($J,"UJOFDA"))) ; External Values, Transaction
 ; ZEXCEPT: DIERR ; Fileman variable cleaned in CLEAN^DILF call
 I $D(DIERR) W "An error occured",! S $EC=",U1,"
 W !!
 QUIT

Attachments (1)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.