@@ -477,7 +477,10 @@ def _ws_compute_hashed_password(self, hash_type: str, password: str, serial: str
477477 # initially found @ https://github.com/joscha82/wattpilot/issues/46#issuecomment-3289810024
478478 # and then took it from wattpilot!
479479 # https://github.com/mk-maddin/wattpilot-HA/blob/master/custom_components/wattpilot/wattpilot/src/wattpilot/__init__.py#L498
480- return self .__bcrypt_hash_password (password , serial ).encode ()
480+ iterations = 8
481+ password_hash = hashlib .sha256 (password .encode ()).hexdigest ()
482+ salt = f"$2a${ iterations :02d} ${ bcryptjs_encode_base64 (serial , 16 )} "
483+ return bcrypt .hashpw (password_hash .encode (), salt .encode ())[len (salt ):]
481484
482485 return None
483486
@@ -767,68 +770,44 @@ def extract_ws_message_data(self, data:dict):
767770
768771 return new_data_arrived
769772
770- def __bcryptjs_base64_encode (self ,b : bytes , length : int ) -> str :
771- BASE64_CODE = list ("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" )
772- off = 0
773- rs = []
774-
775- if length <= 0 or length > len (b ):
776- raise ValueError (f"Illegal len: { length } " )
777-
778- while off < length :
779- c1 = b [off ] & 0xff
780- off += 1
781- rs .append (BASE64_CODE [(c1 >> 2 ) & 0x3f ])
782- c1 = (c1 & 0x03 ) << 4
783- if off >= length :
784- rs .append (BASE64_CODE [c1 & 0x3f ])
785- break
773+ @staticmethod
774+ def bcryptjs_base64_encode (b : bytes , length : int ) -> str :
775+ BASE64_CODE = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
786776
787- c2 = b [off ] & 0xff
788- off += 1
789- c1 |= (c2 >> 4 ) & 0x0f
790- rs .append (BASE64_CODE [c1 & 0x3f ])
791- c1 = (c2 & 0x0f ) << 2
792- if off >= length :
793- rs .append (BASE64_CODE [c1 & 0x3f ])
794- break
777+ if not 0 < length <= len (b ):
778+ raise ValueError (f"Illegal len: { length } " )
779+
780+ rs = []
781+ i = 0
782+
783+ while i < length :
784+ c1 = (b [i ] & 0x03 ) << 4
785+ rs .append (BASE64_CODE [b [i ] >> 2 ])
786+ i += 1
787+
788+ if i >= length :
789+ rs .append (BASE64_CODE [c1 ])
790+ break
791+
792+ rs .append (BASE64_CODE [c1 | (b [i ] >> 4 )])
793+ c1 = (b [i ] & 0x0f ) << 2
794+ i += 1
795+
796+ if i >= length :
797+ rs .append (BASE64_CODE [c1 ])
798+ break
799+
800+ rs .append (BASE64_CODE [c1 | (b [i ] >> 6 )])
801+ rs .append (BASE64_CODE [b [i ] & 0x3f ])
802+ i += 1
803+
804+ return "" .join (rs )
805+
806+ @staticmethod
807+ def bcryptjs_encode_base64 (s : str , length : int ) -> str :
808+ if not s .isdigit ():
809+ _LOGGER .warning (f"bcryptjs_encode_base64(): check serial string - should be digits only: { s } " )
810+ raise ValueError (f"Check serial string - should be digits only: { s } " )
795811
796- c2 = b [off ] & 0xff
797- off += 1
798- c1 |= (c2 >> 6 ) & 0x03
799- rs .append (BASE64_CODE [c1 & 0x3f ])
800- rs .append (BASE64_CODE [c2 & 0x3f ])
801-
802- return "" .join (rs )
803-
804- def __bcryptjs_encode_base64 (self , s : str , length : int ) -> str :
805- if s .isdigit (): #numeric only serial
806- vals = [ord (ch ) - ord ('0' ) for ch in s ]
807- b = bytes ([0 ] * (length - len (vals )) + vals )
808- else : #not sure about serials in future - fallback
809- _LOGGER .warning (f"__bcryptjs_encodeBase64: check serial string - should be digits only: { s } " )
810- raise ValueError (f"Check serial string - should be digits only: { s } " )
811-
812- return self .__bcryptjs_base64_encode (b ,length )
813-
814- def __bcrypt_hash_password (self , password , serial , iterations = 8 ) -> str :
815- password_hash_sha256 = hashlib .sha256 (password .encode ('utf-8' )).hexdigest ()
816- serial_b64 = self .__bcryptjs_encode_base64 (serial , 16 )
817-
818- # don't remove this (or do not simplify this to
819- # salt = ["$2a$"]
820- salt = []
821- salt .append ("$2a$" )
822-
823- if iterations < 10 :
824- salt .append ("0" )
825- salt .append (str (iterations ))
826- salt .append ("$" )
827- salt .append (serial_b64 )
828- salt = '' .join (salt )
829- bsalt = salt .encode ("utf-8" )
830- bpassword = password_hash_sha256 .encode ('utf-8' )
831- pwhash = bcrypt .hashpw (bpassword , bsalt )
832- salt_length = len (salt )
833- pwhash_sub = pwhash [salt_length :].decode ('ascii' )
834- return pwhash_sub
812+ b = bytes ([0 ] * (length - len (s )) + [int (ch ) for ch in s ])
813+ return bcryptjs_base64_encode (b , length )
0 commit comments